2020-12-30 18:15 GMT, Rowan Tommins <rowan.coll...@gmail.com>: > On 30/12/2020 13:49, Olle Härstedt wrote: >> Uniqueness is when you only allow _one_ reference to an object (or >> bucket of memory). >> [...] >> >> You can compare a builder pattern with immutability vs non-aliasing >> (uniqueness): >> >> ``` >> // Immutable >> $b = new Builder(); >> $b = $b->withFoo()->withBar()->withBaz(); >> myfun($b); // $b is immutable, so $b cannot be modified by myfun() >> return $b; >> ``` >> >> ``` >> // Uniqueness >> $b = new Builder(); // class Builder is annotated as non-aliasing/unique >> $b->addFoo(); >> $b->addBar(); >> $b->addBaz(); >> myfun(clone $b); // HAVE TO CLONE TO NOT THROW EXCEPTION. >> return $b; >> ``` > > > Thanks, I can see how that solves a lot of the same problems, in a very > robustly analysable way. > > However, from a high-level user-friendliness point of view, I think > "withX" methods are actually more natural than explicitly cloning > mutable objects. > > Consider the case of defining a range: firstly, with plain integers and > familiar operators: > > $start = 1; > $end = $start + 5; > > This models integers as immutable values, and + as an operator which > returns a new instance. If integers were mutable but not aliasable, we > would instead write something like this: > > $start = 1; > $end = clone $start; > $end += 5; // where += would be an in-place modification, not a > short-hand for assignment > > I think the first more naturally expresses the desired algorithm. It's > therefore natural to want the same for a range of dates: > > $start = MyDate::today(); > $end = $start->withAddedDays(5); > > vs > > $start = MyDate::today(); > $end = clone $start; > $end->addDays(5);
Sure, this is a good use-case for immutability. :) > > > To put it a different way, value types naturally form *expressions*, > which mutable objects model clumsily. It would be very tedious if we had > to avoid accidentally mutating the speed of light: > > $e = (clone $m) * ((clone $c) ** 2); Using a variable on right-hand side does not automatically create an alias, so in the above case you don't have to use clone. A more motivating example for uniqueness is perhaps a query builder. ``` $query = (new Query()) ->select(1) ->from('foo') ->where(...) ->orderBy(..) ->limit(); doSomething($query); doSomethingElse($query); ``` In the above snippet, we don't know if doSomething() will change $query and cause a bug. The issue can be solved with an immutable builder, using withSelect(), withWhere(), etc, OR it's solved with uniqueness, forcing a clone to avoid creating a new alias (passing $query to a function creates an alias inside that function). The optimisation is the same as in my previous example, avoiding copying $query multiple times during build-up. > > >> The guarantee in both above snippets is that myfun() DOES NOT modify >> $b before returning it. BUT with immutability, you have to copy $b >> three times, with uniqueness only one. > > > I wonder if that difference can be optimised out by the > compiler/OpCache: detect clones that immediately replace their original, > and optimise it to an in-place modification. In other words, compile > $foo = clone $foo with { x: 42 } to $foo->x = 42, even if the clone is > actually in a "withX" method. I guess OCaml/Haskell does stuff like this, since everything is immutable by default there. Let's ask them? Unless someone here already knows? :) Olle -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php