This Feature Request is about the implementation of lazy statements.
Basically it should implements a 'lazy' keyword (or similar) and
structured like an anonymous function, except that it doesn't accepts
arguments, but accept the use() feature.
$var = lazy { return 1; }
$var = lazy use ($x) { return $x; }
It differ from anonymous function because this assignment is treated
as variable, without need to call or check if it is a valid Closure
all the time.
Execution example:
echo $var;
echo $var;
In this example, the first echo will execute the lazy statement and
set the returned value directly to $var container, then will echo 1.
The second echo will not execute the lazy anymore, because it was
processed on first echo, so will just echo 1.
It should be very useful on framework or package development because
you can defines lazy properties that need to be calculated only when
it is requested the first time.
Should be useful too when you have a global variable like $user that
you should not use all the time (in all requests).
View::share('user', lazy { return User::find(session('user.id')); });
This code will be executed only on try read or write on $user variable.
It should works from read or write, to the next code is valid:
$var = lazy { return [ 1, 2, 3 ]; };
$var[] = 4;
Currently it is possible do it in a very hackish way: by using magic
getter, by create a user getter or a own implementation of this
feature.
All this ways have a lot of limitations.
A hackish example that shows how it should works on current PHP
version: http://pastebin.com/cz93wL7n
I tell more about that here: https://bugs.php.net/bug.php?id=72637
--
David Rodrigues
This Feature Request is about the implementation of lazy statements.
Basically it should implements a 'lazy' keyword (or similar) and
structured like an anonymous function, except that it doesn't accepts
arguments, but accept the use() feature.$var = lazy { return 1; } $var = lazy use ($x) { return $x; }
It differ from anonymous function because this assignment is treated
as variable, without need to call or check if it is a valid Closure
all the time.
Execution example:
echo $var; echo $var;
In this example, the first echo will execute the lazy statement and
set the returned value directly to $var container, then will echo 1.
The second echo will not execute the lazy anymore, because it was
processed on first echo, so will just echo 1.
It should be very useful on framework or package development because
you can defines lazy properties that need to be calculated only when
it is requested the first time.
Should be useful too when you have a global variable like $user that
you should not use all the time (in all requests).View::share('user', lazy { return User::find(session('user.id')); });
This code will be executed only on try read or write on $user variable.
It should works from read or write, to the next code is valid:
$var = lazy { return [ 1, 2, 3 ]; }; $var[] = 4;
Currently it is possible do it in a very hackish way: by using magic
getter, by create a user getter or a own implementation of this
feature.
All this ways have a lot of limitations.A hackish example that shows how it should works on current PHP
version: http://pastebin.com/cz93wL7n
I tell more about that here: https://bugs.php.net/bug.php?id=72637
--
David Rodrigues
In concept I like the idea, especially as it would allow for "optionally
computed values", which is a performance benefit if those values may not
be used. Think of pulling default values out of configuration in the
database if not provided at runtime; no sense running the query if it
won't be used. I could also see it having very interesting use cases in
dependency injection containers.
However, that also means there's an enormous potential for race
conditions. Technically no more than closures now, but the use cases
seem like they'd make it more prevalent. If you "use" by reference, or
use a mutable object, or a service that depends on IO, etc. then the
resulting value of the lazy variable is not technically
non-deterministic, but will certainly feel like it to the developer.
Is there a way we could provide better safety around that issue?
--Larry Garfield
However, that also means there's an enormous potential for race
conditions. Technically no more than closures now, but the use cases
seem like they'd make it more prevalent. If you "use" by reference, or
use a mutable object, or a service that depends on IO, etc. then the
resulting value of the lazy variable is not technically
non-deterministic, but will certainly feel like it to the developer.
I think that lazy statements should not be used when you can have
possibilities like that. It should not be a substitute to methods.
So, for instance, if I have a mutable dependency, is better I create
a method to do that, that could work better with that.
I can think about how we can handle this, but I need a real case
where this problem can exists. Can you help with this?
Thanks!
Hi David,
This Feature Request is about the implementation of lazy statements.
This looks to solve a problem very similar to the one solved by the
'Memoize' annotation in HHVM -
https://docs.hhvm.com/hack/attributes/special#__memoize
However the memoize functionality seems a lot clearer to me, as it's
less 'magic'; it doesn't make accessing a variable actually call a
function.
Larry Garfield wrote:
However, that also means there's an enormous potential for race
conditions.
I think s/race conditions/circular dependencies/ probably explain the
problem more clearly.
I can think about how we can handle this, but I need a real case
where this problem can exists.
This code has a circular dependency:
class Foo {
public $a, $b;
public __construct() {
$this->a = lazy { return 1 + $this->b; }
$this->b = lazy { return 1 + $this->a; }
}
}
$foo = new Foo;
echo $foo->a;
$foo->a can't be resolved until $foo->b is resolved, and $foo->b can't
be resolved until $foo->a is resolved.
Although that is obvious in a trivial code example, when trying to use
a magic feature like this in complex code, where the circular
dependency involves, say, 10 items, then it become very hard to reason
about the code.
Because the 'memoization' of functions is less magic, it is much less
prone to this circular dependency problem.
cheers
Dan
Hi!
This Feature Request is about the implementation of lazy statements.
Basically it should implements a 'lazy' keyword (or similar) and
structured like an anonymous function, except that it doesn't accepts
arguments, but accept the use() feature.$var = lazy { return 1; }
Is this just keystroke saving for function() or is this supposed to do
something additional?
--
Stas Malyshev
smalyshev@gmail.com
Dan Ackroyd wrote:
This looks to solve a problem very similar to the one solved by the
'Memoize' annotation in HHVM -
https://docs.hhvm.com/hack/attributes/special#__memoize
However the memoize functionality seems a lot clearer to me, as it's
less 'magic'; it doesn't make accessing a variable actually call a
function.
On really it seems a very good feature focused on method call.
Maybe it can be done in same idea, by freezing the first result of method.
But I think hard do that with args.
Maybe something like that: http://pastebin.com/YKjEYeYF
Note: I know that is better an IoC, understand just as example.
Dan Ackroyd wrote:
This code has a circular dependency:
class Foo {
public $a, $b;
public __construct() {
$this->a = lazy { return 1 + $this->b; }
$this->b = lazy { return 1 + $this->a; }
}
}$foo = new Foo;
echo $foo->a;$foo->a can't be resolved until $foo->b is resolved, and $foo->b can't
be resolved until $foo->a is resolved.
Now back to topic, this circular dependency too occur with functions.
Basically if it happen, should throw a infinite recursion error.
(Currently I guess that PHP don't do that, but xdebug does)
In all case, $a should not depends of $b, when $b depends $a.
In this case, should have a bootstrap system, that turn it independent.
Example:
class Foo {
public $a, $b;
public __construct () {
$c = lazy { return generalDependency(); }
$this->a = lazy use ($c) { return 1 + $c; };
$this->b = lazy use ($c) { return 1 + $c; };
}
}
Dan Ackroyd wrote:
Because the 'memoization' of functions is less magic, it is much less
prone to this circular dependency problem.
I guess that memoization is another topic, but still very interesting,
as cited above.
So maybe it can be discussed apart of this email, if is reasonable.
Stanislav Malyshev wrote:
This Feature Request is about the implementation of lazy statements.
Basically it should implements a 'lazy' keyword (or similar) and
structured like an anonymous function, except that it doesn't accepts
arguments, but accept the use() feature.$var = lazy { return 1; }
Is this just keystroke saving for function() or is this supposed to do
something additional?
It's similar to function (), but should be applied to variables or
properties only.
But exists an important difference between you use lazy vs function ().
$var1 = function () { sleep(5); return 1; }
$var2 = lazy { sleep(5); return 1; }
-
$var1 should be called like $var1();
-
If I call $var1() five times, it'll execute this structure over 25 seconds;
-
If I try to write over $var1(), possible I should get an error
trying to write over int (1); -
$var2 should be called like $var2 directly;
-
If I call $var2 five times, it'll execute once the structure,
custing 5 seconds only; -
If I try to write over $var2 it'll, in fact, write over $var2
processed structure, that stores int (1);
Now back to topic, this circular dependency too occur with functions.
Yes, but the difference is in the surprise factor.
Two functions having a circular dependency ==> not too astonishing,
and easy to think about.
Reading the value of a variable having a circular dependency ==> give
up programming to become a farmer.
cheers
Dan
https://en.wikipedia.org/wiki/Principle_of_least_astonishment
Dan Ackroyd wrote:
Yes, but the difference is in the surprise factor.
Two functions having a circular dependency ==> not too astonishing,
and easy to think about.Reading the value of a variable having a circular dependency ==> give
up programming to become a farmer.
Okay, so we can avoid circular dependencies (in sense of find
where a circular dependency occur) by creating a progression
system over lazy objects.
Something like:
Lazy {
progress { DEFINED, EXECUTING, FINISHED };
statement
}
For instance, when you defines a lazy statement, it states is
set as "defined". When you starts execution over it, it changes
the states to "executing". After it finish, it change states to
"finished". This last state is the final value of variable, basically.
So, in case of a lazy statement be called on "executing" phase,
it should throw an error.
For instance:
$b = null;
$a = lazy use ($b) { return $b; }
$b = lazy use ($a) { return $a; }
In this case, both $a and $b are on state "defined".
echo $a;
When I call $a now, it set states to "executing".
In this case, it'll try read $b that is lazy too.
$b is now "executing" too.
Now $b will try to read $a, but $a already is "executing"
So PHP should throw an error like "cyclic execution on line x".
But in all way, lazy statements should not be used on all places
I should not substitute default variable, mainly because it should
be less performatic (not too much, I guess).
Lazy statements should be used only in case where the
variable contents is very complex (high cost) and very few
useful in all requests. And where methods or functions is not too
viable.
Example:
class Foo {
public $something = lazy {
// do complex things.
return $complexValue;
};
}
$foo = new Foo;
$foo->something++;
Instead of:
class Foo {
private $something;
public function getSomething() {
if (!$this->something) {
// do complex things.
$this->something = $complexValue;
}
return $this->something;
}
public function setSomething($value) {
$this->something = $value;
}
}
$foo = new Foo;
$fooSomething = $foo->getSomething();
$fooSomething++;
$foo->setSomething($fooSomething);