On 30/01/2015 13:07, Alexander Lisachenko wrote:
What is wrong here? Emulated immutability. All methods will create a
separate instance of request, so
$baseRequest->withUri()->withMethod()->withHeader()->withBody() will create
total 5 object instances. It's a memory overhead and time consumption for
each method call.
Like Andrea, I don't see how immutable variables/objects solve this
problem. The overhead is not from emulating the immutability, it is a
consequence of the design pattern choosing immutability. In fact, from
the code shown, there is no way to know that immutability is in any way
relevant, since the definition of withUri() could be a mutator which
ends "return $this", a pattern I've frequently used to create such
"fluent" interfaces. Copy-on-write doesn't help, either, since all 5
calls are writes, so will still make 5 copies.
What I want to discuss is true immutability flag for variables and
parameters. There are a lot of languages that use "final" or "const"
keywords to prevent modification of variables.
On the concrete suggestion, though, I do think there are possible
advantages to this. I've long felt the confusion people have over
pass-by-value for primitives, pass-by-value-which-is-actually-a-pointer
for objects, and pass-by-reference for a variable which might or might
not be an object pointer, is a failure not of the programmer but of the
programming language. In a blog post about it a few years ago [1], I
suggested that deep immutability (along with deep cloning) could provide
a better framework than by-value vs by-reference in modern OO languages.
This is rather different from defining a *type* that is immutable, since
it implies temporary immutability of a particular instance; but it seems
to be what at least some of your examples are hinting at.
The problem is that deep cloning and deep immutability are non-trivial;
PHP notably doesn't support deep cloning of objects, requiring each
class to define what to do with any non-primitive members, since some
may represent resources which can't be meaningfully cloned just by
copying data in memory.
In the same way, in order to make an instance deeply immutable, you need
to nail down the details of which actions are actually allowed while
it's in that state, and that gets complicated:
- Directly assigning to a public property is clearly invalid, as is
calling a method on that property which mutates it.
- If the property is itself an object, assigning it to a temporary
variable must retain the immutability - that is, we don't want
"$foo->bar->setValue(42);" to behave differently from "$x = $foo->bar;
$x->setValue(42);"
- Calling a method which internally performs such an action is also
invalid (like the setValue() in the example above). This could be
achieved by executing the function but marking $this as immutable, but
that means that the method may have other effects up to the point where
the violation occurs, so it would be preferable to somehow invalidate
the method call.
- For objects which represent proxies for external resources, there may
be methods which mutate that external state, and properties which exist
only as virtual getters. So calling $db->insertRow(...) should probably
be invalid, since it changes the value of $db->lastInsertedId.
I think the only way to do it would be for every method to be invalid
unless it is explicitly marked as "immutable-safe", e.g.
class Foo {
private $delegatedObject;
public function __construct() { $this->delegatedObject = new
SomethingElse; }
immutable public function getValue() { return
$this->delegatedObject->getX(); }
public function setValue($newValue) { echo "Setting!";
$this->delegatedObject->setX($newValue); }
}
const $foo = new Foo;
echo $foo->getValue();
$foo->setValue(42); // Instantly fails without echoing "Setting!", but
method is illegal in immutable context
Copy-on-write, at the user object level, requires both this *and* deep
cloning:
$bar = clone-on-write $foo;
$bar->setValue(42); // Needs to implicitly clone $foo before calling
setValue(), but must also clone $delegatedObject for that to be meaningful
So class Foo needs an implementation of __clone() as well before any of
this can be used, and if it is missing, the resulting behaviour may be
rather non-obvious.
As ever, the devil's in the detail. The only language I know of that's
meaningfully tackled this is Rust, with its concepts of "boxes" and
"lending", although I'm hazy on the details.
[1]: http://rwec.co.uk/blog/2010/08/object-references-are-confusing/
Regards,
--
Rowan Collins
[IMSoP]
--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php