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

Reply via email to