Hello Internals,
our new closures can easily work as prototypes and I actually thought
of simply doing it as a bug fix. But it appeared that my first attempt
was inclomplete if not to say completely wrong. It further more turned
out that not everyone is a fan of the genral idea.
Basically what I expected is that closures assigned to object properties
behave in the same way prototypes behave. That is:
-
a non static closure assigned to an instance changes the closures
this to be set to the actual object:
$closure = function() {}
// $closure->this ==NULL
$obj1->property = $closure;
// $obj1->property->this == $obj1;
// $closure->this ==NULL
-
a static closure assigned to an instance sets the closure's scope
1+2) both give access to private members
-
Assignment of a closure from one object property to another objects
property changes it $this.
$obj2->property = $obj1->property;
// $obj1->property->this == $obj1
// $obj2->property->this == $obj2 -
Cloning an object assigns the properties correct. Right now we
increase the refcount only instead of cloning as that is the default
behavior of cloning. Since a normal variable splits on changes nothing
ever notices this. For oject types that do not support cloning this is
very different though. Now we cannot simply add cloning as then we'd
further change closure behavior. And this way we would not fix the this
pointer.
$obj3 = clone $obj;
// $obj3>property->this == $obj3 -
A closure assigned to a property can be called directly rather
than just by a temporary variable:
$obj->property = function() {}
$obj->property(); // valid call
The above requires the following internal modifications:
-
Closures internally support clone, but do not set the actual handler,
thus preventing anything else from cloning them (current behavior). -
Default object cloning does not only call add_ref per member variable
but also checks for a member being a prototype method in which case it
performs the necessary cloning of that property. -
An additonal check in the default property get handler is reaquired
to check for a closure property (prototype) in case a function cannot be
found. Unless someone uses closures there will be no change in behavior.
If this gets done later we will change behavior.
Why did I work on this:
-
Because to me the current implementation is broken. While as closures
themselves work perfect, they simply work very unexpected when assigned
to an object. -
The current behavior seems inconsistent as it matters where an
assignment of a closure to a proeprty is being performed. OR how a closure
is being created. -
The current behavior can be used as an impleentation of prototype
methods in PHP. However it just does a very tiny subset that makes it
a broken implementation. So we will end up in a situation where users see
prototype methods but don't get them to work. -
We could finally get more consistency because we would have a partial
protoype ojbect model not only for member variables but also for member
functions.
Issues that speak against this:
-
A closure bound to a property is case sensitive unlike a member
function. We have this however anyway. As we already have functions
callable by variables whether the variable is a closure or just a
function name. -
To close to a release. Well that's why we have beta cycles to identify
tsituations like this. -
If closures get callable by property directly then we end up in a
situation where we can have two methods with the same name. That means it
is discussable whether we want to allow assignment of a closure to a
member variable name that already exists as a private member function.
Best regards,
Marcus
Hi!
- a non static closure assigned to an instance changes the closures
this to be set to the actual object:
I'm not sure why would you expect this. If you have closure that has
some bound $var inside, and you use it in context which has another
$var, you don't expect that $var change to new scope's $var, do you?
I always thought of closures as taking their variables and context with
them, not changing them each time caller changes.
Also, this adds very new thing to PHP - objects that change while being
assigned. I am not sure it is a good thing.
- Cloning an object assigns the properties correct. Right now we
increase the refcount only instead of cloning as that is the default
behavior of cloning. Since a normal variable splits on changes nothing
ever notices this. For oject types that do not support cloning this is
very different though. Now we cannot simply add cloning as then we'd
further change closure behavior. And this way we would not fix the this
pointer.
Besides the issue above with changing $this I'm not sure what would
proper clone do - i.e. by-val bound variables are by-val anyway, so it
shouldn't matter if they are cloned, and by-ref ones should be connected
anyway so again it doesn't matter. Am I missing something?
BTW, why would one clone closures anyway?
- A closure assigned to a property can be called directly rather
than just by a temporary variable:
$obj->property = function() {}
$obj->property(); // valid call
This will get us into trouble IMHO, as it would not behave too well with
__get/__set and __call. I.e. if we have $foo->nosuchproperty() -
should we call __get or __call? So far variables implemented through
__get behave exactly like ones implemented through definition - but I
don't understand how to reconcile it with this.
I also don't like having special one-class check inside assignment
operators just for this narrow function - it doesn't look like good
generic code and I suspect for proper implementation may require
instanceof check on each assignment - which would be really bad.
- The current behavior seems inconsistent as it matters where an
assignment of a closure to a proeprty is being performed. OR how a closure
is being created.
Of course it matters how (or, more precisely, where) the closure was
created - isn't it the whole point of closure?
- If closures get callable by property directly then we end up in a
situation where we can have two methods with the same name. That means it
is discussable whether we want to allow assignment of a closure to a
member variable name that already exists as a private member function.
This is another thing - does it mean on each assignment we'd have to
check if member function with same name doesn't exist? That might break
some code in interesting ways too.
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
Hello,
Hi!
- a non static closure assigned to an instance changes the closures
this to be set to the actual object:I'm not sure why would you expect this. If you have closure that has some
bound $var inside, and you use it in context which has another $var, you
don't expect that $var change to new scope's $var, do you?
I always thought of closures as taking their variables and context with
them, not changing them each time caller changes.Also, this adds very new thing to PHP - objects that change while being
assigned. I am not sure it is a good thing.
- Cloning an object assigns the properties correct. Right now we
increase the refcount only instead of cloning as that is the default
behavior of cloning. Since a normal variable splits on changes nothing
ever notices this. For oject types that do not support cloning this is
very different though. Now we cannot simply add cloning as then we'd
further change closure behavior. And this way we would not fix the this
pointer.Besides the issue above with changing $this I'm not sure what would proper
clone do - i.e. by-val bound variables are by-val anyway, so it shouldn't
matter if they are cloned, and by-ref ones should be connected anyway so
again it doesn't matter. Am I missing something?
BTW, why would one clone closures anyway?
- A closure assigned to a property can be called directly rather
than just by a temporary variable:
$obj->property = function() {}
$obj->property(); // valid callThis will get us into trouble IMHO, as it would not behave too well with
__get/__set and __call. I.e. if we have $foo->nosuchproperty() - should we
call __get or __call? So far variables implemented through __get behave
exactly like ones implemented through definition - but I don't understand
how to reconcile it with this.I also don't like having special one-class check inside assignment operators
just for this narrow function - it doesn't look like good generic code and I
suspect for proper implementation may require instanceof check on each
assignment - which would be really bad.
- The current behavior seems inconsistent as it matters where an
assignment of a closure to a proeprty is being performed. OR how a closure
is being created.Of course it matters how (or, more precisely, where) the closure was created
- isn't it the whole point of closure?
- If closures get callable by property directly then we end up in a
situation where we can have two methods with the same name. That means it
is discussable whether we want to allow assignment of a closure to a
member variable name that already exists as a private member function.This is another thing - does it mean on each assignment we'd have to check
if member function with same name doesn't exist? That might break some code
in interesting ways too.
It will not be able to respect the inheritance rules as well (at least
how it is implemented currently) :
class A {
public function plop(){}
}
class B extends A {}
$b = new B; $b->plop = function(){};
$b->plop(); //will still call A::plop().
And if by any chance there is a way to make that work, it will still
cause trouble with internal classes that checks for inherited methods
at instantiation only.
Looks like it would either introduce a lot of inconsistencies, or a
lot of edge cases to fix by adding exceptions in the code.
Regards,
--
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com--
--
Etienne Kneuss
http://www.colder.ch
Men never do evil so completely and cheerfully as
when they do it from a religious conviction.
-- Pascal
Hello Stanislav,
Tuesday, January 13, 2009, 12:07:31 AM, you wrote:
Hi!
- a non static closure assigned to an instance changes the closures
this to be set to the actual object:
I'm not sure why would you expect this. If you have closure that has
some bound $var inside, and you use it in context which has another
$var, you don't expect that $var change to new scope's $var, do you?
I always thought of closures as taking their variables and context with
them, not changing them each time caller changes.
Variables and this are two very different things in PHP and even more so
for closures. Because there this does not reflect the object itself but the
object it is bound to.
Also, this adds very new thing to PHP - objects that change while being
assigned. I am not sure it is a good thing.
Well Closures are a brand new thing in PHP. So far we had nothing even
remotely close to the closures we have right now.
- Cloning an object assigns the properties correct. Right now we
increase the refcount only instead of cloning as that is the default
behavior of cloning. Since a normal variable splits on changes nothing
ever notices this. For oject types that do not support cloning this is
very different though. Now we cannot simply add cloning as then we'd
further change closure behavior. And this way we would not fix the this
pointer.
Besides the issue above with changing $this I'm not sure what would
proper clone do - i.e. by-val bound variables are by-val anyway, so it
shouldn't matter if they are cloned, and by-ref ones should be connected
anyway so again it doesn't matter. Am I missing something?
BTW, why would one clone closures anyway?
I Was not at all speaking of static variables. In fact I suggest we keep
them exactly as they are. Which is probably what you want, based on what
you wrote. And in my opinion it also makes the most sense. A closure keeps
its state.
- A closure assigned to a property can be called directly rather
than just by a temporary variable:
$obj->property = function() {}
$obj->property(); // valid call
This will get us into trouble IMHO, as it would not behave too well with
__get/__set and __call. I.e. if we have $foo->nosuchproperty() -
should we call __get or __call? So far variables implemented through
__get behave exactly like ones implemented through definition - but I
don't understand how to reconcile it with this.
Why would you call __get() here? Becasue I did that by mistake in my very
first mail? You clearly have a function call and thus you only go for
__call(). As of today you can already do:
function __call($name, $args) {
if ($this->properties[$name] instanceof 'Closure') {
return call_user_func_array($this->property[$name], $args);
}
}
Now we already have callable properties - directly callable.
I also don't like having special one-class check inside assignment
operators just for this narrow function - it doesn't look like good
generic code and I suspect for proper implementation may require
instanceof check on each assignment - which would be really bad.
No, becasue Closure cannot be derived as it is a final class. If we change
that later then we simply have to change the check to test for the handler.
We might want to do that anyway as it is faster.
- The current behavior seems inconsistent as it matters where an
assignment of a closure to a proeprty is being performed. OR how a closure
is being created.
Of course it matters how (or, more precisely, where) the closure was
created - isn't it the whole point of closure?
It matters how you bind static variables to it as they are taken from the
context. And by the binding you keep the context. Sure all right. But we
bind this in a completely different way.
And when I bind a closure to an object inside that object why should it be
different from an assignment outside the class? And it gets even better, if
you assign that closure to another object it gets private access to the
other classes members; plus you can bind it to the new classes private
members as well. This is a nightmare and plain wrong to me and the part
with accessing another classes private members without knowing will create
a very large WTF factor and conflicts with everythign we ever said about
visibility.
- If closures get callable by property directly then we end up in a
situation where we can have two methods with the same name. That means it
is discussable whether we want to allow assignment of a closure to a
member variable name that already exists as a private member function.
This is another thing - does it mean on each assignment we'd have to
check if member function with same name doesn't exist? That might break
some code in interesting ways too.
I don't see a reason for this. But people might differ. And at the end of
the day the problems arising from this are the least evil.
Best regards,
Marcus
Hi!
Also, this adds very new thing to PHP - objects that change while being
assigned. I am not sure it is a good thing.Well Closures are a brand new thing in PHP. So far we had nothing even
remotely close to the closures we have right now.
There are a lot of different features in PHP, that's not the reason to
turn the engine into a salad of special cases and exceptions. That's why
making Closure an object and having __invoke was a good idea - because
it fits what the engine already does very well. Having special case just
for one class doesn't fit it so well.
you wrote. And in my opinion it also makes the most sense. A closure keeps
its state.
I consider $this be a part of this state. Maybe if you really need it
it'd be fine to do something like $closure->setThis($object)...
Why would you call __get() here? Becasue I did that by mistake in my very
Imagine such code:
class Contains {
private $_store;
function __set($n, $v) {
$this->_store[$n] = $v;
}
function __get($n) {
return $this->_store[$n];
}
}
Pretty standard class. Now imagine you do this:
$c = new Contains();
$c->foo = "bar";
echo $c->foo;
works well and basically you see Contains as regular object and couldn't
care less it has handlers and not properties. Now this:
$c->bar = function() { }
$c->bar();
what happens here? You can't get the value of "bar" without calling
__get and if you call __call instead that means you either lost $c->bar
or you have two kinds of properties now - ones that call __get and ones
that don't. I don't see how it is good.
No, becasue Closure cannot be derived as it is a final class. If we change
Why it's a final class? Any special reason for that?
It matters how you bind static variables to it as they are taken from the
context. And by the binding you keep the context. Sure all right. But we
bind this in a completely different way.
I see no reason to have two contexts in closure - one bound one way and
another bound another way. I think context is a context - it's where the
closure was created.
different from an assignment outside the class? And it gets even better, if
you assign that closure to another object it gets private access to the
other classes members; plus you can bind it to the new classes private
That's the whole point of the closure - if you make object-bound closure
you can pass accessors to private variables to external classes. If you
don't want it - either don't access privates in your closure or make the
closure static. The whole point of the closure is that you can keep the
context with the closure and thus give other scope regulated access to
your scope.
I don't see a reason for this. But people might differ. And at the end of
the day the problems arising from this are the least evil.
The least between what and what? I don't see any problem or "evil" with
what we have now except for missing exotic feature of rebinding closures
- which would be a huge WTF for me if all closures worked this way (I
definitely expect closure to keep its context and not switch it each
time I pass it around), but if there was a special way to make it work
this way (as setThis mentioned above or any other way) and it would not
conflict with other features and not require dirty hacks on the engine I
wouldn't mind.
--
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
just a note about it
in javascript, "this" ($this in php) is a special variable in closure,
"this" is always bound to the "object" of "object.method(...)" when it
is called, whenever i want a "this" of closure creation time context,
i use:
var _this = this;
return function() { _this.prop = 123; _this.method(); }
so..
I see no reason to have two contexts in closure - one bound one way and
another bound another way
it's already 2 contexts in php: $this and $_LOCALSCOPE (which is not
available in user code). and now $_CLOSURE context
the reason you think there's only 1 context maybe because $this is
implemented in $_LOCALSCOPE['this'] = &$this in php internally. :)
I think context is a context - it's where the closure was created.
true, the $_CLOSURE context
prototype can be useful in php. and like closure that ppl got used to
javascript version of closure/prototype, there should be no big
problem understanding it.
Hi!
Also, this adds very new thing to PHP - objects that change while
being assigned. I am not sure it is a good thing.Well Closures are a brand new thing in PHP. So far we had nothing even
remotely close to the closures we have right now.There are a lot of different features in PHP, that's not the reason to
turn the engine into a salad of special cases and exceptions. That's why
making Closure an object and having __invoke was a good idea - because
it fits what the engine already does very well. Having special case just
for one class doesn't fit it so well.you wrote. And in my opinion it also makes the most sense. A closure
keeps
its state.I consider $this be a part of this state. Maybe if you really need it
it'd be fine to do something like $closure->setThis($object)...Why would you call __get() here? Becasue I did that by mistake in my very
Imagine such code:
class Contains {
private $_store;function __set($n, $v) {
$this->_store[$n] = $v;
}function __get($n) {
return $this->_store[$n];
}
}Pretty standard class. Now imagine you do this:
$c = new Contains();
$c->foo = "bar";
echo $c->foo;works well and basically you see Contains as regular object and couldn't
care less it has handlers and not properties. Now this:$c->bar = function() { }
$c->bar();what happens here? You can't get the value of "bar" without calling
__get and if you call __call instead that means you either lost $c->bar
or you have two kinds of properties now - ones that call __get and ones
that don't. I don't see how it is good.
That is one example of convoluted code that is already possible. If a
developer creates such a mess is his fault.
As Marcus said, it is already possible to call properties. All we have
to do is implement __call(). Not being forced to implement __call in
cases where we want object augmentation with lambdas is just syntactic
sugar. I agree though that a rebind()/setThis() method on the Closure
object is needed, but $this should be rebound by default.
Auto-magically binding $this to the current object, whatever that may
be, is a missing feature that requires a developer to pass the instance
as an argument to that lambda.
class A
{
protected $_lambdas = array();
public function __set($name, $lambda)
{
$this->_lambdas[$name] = $lambda;
}
public function __call($lambda, $args)
{
$args = array_merge($this, $args);
return call_user_func_array($this->_lambdas[$lambda], $args);
}
}
$a = new A;
$a->foo = function($this) {
var_dump($this);
};
$a->foo();
Now, the above code is already possible, but... by implementing callable
properties a developer is spared the effort of implementing a __call()
method. The only problem now is that by deciding to not implement
__call() he loses a nice opportunity of passing the instance object and
he's left with some ugly alternatives:
class B
{}
$b = new B;
$b->foo = function($this) {
var_dump($this);
};
$b->foo($b);
// how would be the above call different than?
$foo = function($object) {
var_dump($object);
}
$foo($b);
// Or would you believe this is just fine?
$b->foo = function() use ($b) {
var_dump($b);
};
$b->foo();
Sooner or later, people would want that use statement to be dismissed
for an auto-magically bound $this. It's also the problem of accessing
protected and private members.
JavaScript does automatic binding of the "this" instance which is
actually a key point in prototyping. Well, strictly speaking this is not
prototyping, is augmentation. Adding members to the JS prototype object
means that any existing and further instances will share those
members. Augmentation is just about adding members to an existing
instance. Real prototyping is though possible in PHP 5.3:
class A
{
/**
* Lambdas available to all instances
*/
public static $lambda;
public function __call($method, $args)
{
$method = self::$lambda;
return call_user_func_array($method, $args);
}
}
A::$lambda = function() {
return 'foo';
};
$a1 = new A;
$a2 = new A;
echo $a1->lambda();
echo $a2->lambda();
The problem of binding $this appears again, but I believe this use case
is much harder to solve because there's no $this in the moment we add
lambdas to the class. Anyway, it's a point to consider.
No, becasue Closure cannot be derived as it is a final class. If we
changeWhy it's a final class? Any special reason for that?
It matters how you bind static variables to it as they are taken from the
context. And by the binding you keep the context. Sure all right. But we
bind this in a completely different way.I see no reason to have two contexts in closure - one bound one way and
another bound another way. I think context is a context - it's where the
closure was created.different from an assignment outside the class? And it gets even
better, if
you assign that closure to another object it gets private access to the
other classes members; plus you can bind it to the new classes privateThat's the whole point of the closure - if you make object-bound closure
you can pass accessors to private variables to external classes. If you
don't want it - either don't access privates in your closure or make the
closure static. The whole point of the closure is that you can keep the
context with the closure and thus give other scope regulated access to
your scope.
In this particular case I think having callable properties has less to
do with closures (I mean capturing context, not lambdas) and more to do
with extending functionality of existing classes and objects without a
need for creating additional classes. It's also about overriding
existent behavior.
In JavaScript, if you don't have a String.trim() function you add it to
the prototype. From that moment on, all your strings have a trim()
method:
String.prototype.trim = function() {};
Of course, this is nice for primitive types that have some form of
syntactic sugar representation, quotes in case of strings. So now I'm
able to do:
var trimmed = " has spaces ".trim();
This is of course not the case with PHP, but as I said, this feature has
less to do with closures.
I don't see a reason for this. But people might differ. And at the end of
the day the problems arising from this are the least evil.The least between what and what? I don't see any problem or "evil" with
what we have now except for missing exotic feature of rebinding closures
- which would be a huge WTF for me if all closures worked this way (I
definitely expect closure to keep its context and not switch it each
time I pass it around), but if there was a special way to make it work
this way (as setThis mentioned above or any other way) and it would not
conflict with other features and not require dirty hacks on the engine I
wouldn't mind.
Hi!
That is one example of convoluted code that is already possible. If a
developer creates such a mess is his fault.
"Convoluted"? "Mess"? Are you kidding me? It's standard usage of access
handlers.
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
Hi,
Hi!
That is one example of convoluted code that is already possible. If a
developer creates such a mess is his fault."Convoluted"? "Mess"? Are you kidding me? It's standard usage of
access handlers.
I don't understand how that could be standard when the example was about
calling overloaded properties, which isn't yet possible in the language,
as you vigilantly observed.
Now, regardless whether is convoluted or not, you must agree that is
better having callable properties in the language and documenting that
this won't apply for overloaded properties. In the end you don't really
need callable overloaded properties as a developer already has access to
the internals of a class, thus __call() is all he needs to implement
them (there would be at least a magic call anyway). It's a trade off but
a worthy one in my opinion. Just as right now it is being documented
that in order to call a lambda assigned to an instance property you need
a temporary variable, it could be easily documented that one should not
expect callable overloaded properties and must instead implement __call
beside the standard __get/__set.
It's a WTF but a smaller one this time. Anyway, there may be stronger
arguments against so I'm interested in your opinion.
Thanks,
I. Stan
Hi!
Now, regardless whether is convoluted or not, you must agree that is
better having callable properties in the language and documenting that
this won't apply for overloaded properties. In the end you don't really
I do not see any aspect in which it would be better.
need callable overloaded properties as a developer already has access to
Having features in the language that do not work together is usually a
bad idea. Developer using overloads would expect it to work in most
cases where properties work, and if this prototyping design can not do
it I think it is a sign more work is needed to solve it before we can
add it.
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
Hi
I have tried to catch up to the discussion yesterday evening. I must
admit its not very trivial for me to understand the entire scope of
the issues. I am especially worried about things becoming too magical
(although in general I am pro giving end users all the power in the
world). At this point I am beginning to wonder if we maybe should drop
any special OO handling from closures for now and leave it to the next
bigger release to decide if to readd it.
We have worked through the challenge of getting namespaces worked out
in 5.3, adding yet another increasingly heavy feature to the syntax
would delay us if we really want to make sure we get it right.
Screwing up would get us into a world of hurt and to me the main use
case we wanted to address was making it easier to define callbacks for
our various internal functions.
This is just my "managament" level view point given the discussion at
this stage and the goal to get PHP 5.3 out the door without delay from
any feature (since we have enough features to release even if we
remove one or two features).
regards,
Lukas
We have worked through the challenge of getting namespaces worked out
in 5.3, adding yet another increasingly heavy feature to the syntax
would delay us if we really want to make sure we get it right.
Screwing up would get us into a world of hurt and to me the main use
case we wanted to address was making it easier to define callbacks for
our various internal functions.
I was wondering, why don't you simply mark the feature as "experimental", so people that use it know that it might change unexpectedly from one version to another, breaking their scripts. Something similar to the Linux Kernel features approach.
I've never seen this approach used in PHP development process, I've always been seeing the "feature freeze -> support for a long time -> deprecate -> remove in the next major version" approach. Marking a particular language feature as experimental would allow you to drop it or to change it without notice even in a patchlevel release (5.3.1), resulting in a way quicker and more dynamic development process.
The benefits? Nobody (but me...) would install an unstable version of PHP in a production environment, but probably some people would taste an experimental feature included in a stable version.
Just my 2 cents.
Regards,
--
Giovanni Giacobbi
Hello Stanislav,
Wednesday, January 14, 2009, 5:29:02 PM, you wrote:
Hi!
That is one example of convoluted code that is already possible. If a
developer creates such a mess is his fault.
"Convoluted"? "Mess"? Are you kidding me? It's standard usage of access
handlers.
It is a mess right now. You assign a closure to another method and get
access to the original owners private members. That is not only unexpected
and contradicting anything that any oyther language ever but but also
violates our very own basic ideas. That is what I call a mess.
Best regards,
Marcus
Hi Marcus,
"Convoluted"? "Mess"? Are you kidding me? It's standard usage of access
handlers.It is a mess right now. You assign a closure to another method and get
access to the original owners private members. That is not only unexpected
and contradicting anything that any oyther language ever but but also
violates our very own basic ideas. That is what I call a mess.
You could also call Javascript's behaviour confusing. A closure is per
definition a function that encloses its scope at creation time. E.g. if
you have the following (in Javascript, PHP, doesn't matter):
var foo;
var closure = function () {
alert (foo);
};
The current scope variable foo is inherited. The question is: Why
shouldn't the same also happen for the variable this? In the closures
implementatios Dmitry and I designed for PHP, it does. Admittedly, $this
is a special variable because it's implicitly available in normal
methods and thus we decided that for closures you don't need to do "use
($this)" either.
So the question is: Why does Javascript change the pointer to the this
variable upon calling a method? The answer is simple: Because there is
NO OTHER WAY to define object methods in Javascript. You always have
to use object.method = function () { }; or Something.prototype.method =
... in order to define a callable method. There is no other way. Because
of that, Javascript defines the behaviour with $this.
PHP, on the other hand, since it already does have a method for creating
normal class methods (simply define them in the class { } block), does
not need such a mechanism for normal OOP.
Also, implementing this in PHP may give quite some headaches. Take for
example the following code:
interface Some_Filter {
public function accept ($value);
}
class Closure_Filter implements Some_Filter {
private $closure;
public function __construct (Closure $closure) {
$this->closure = $closure;
}
public function accept ($value) {
// Or something similar, see below
return call_user_func ($this->closure, $value);
}
}
class Foo {
private $min, $max;
public function bar () {
$filter = new Closure_Filter (function ($value) {
return $value >= $this->min && $value <= $this->max;
});
$data = $something->doSomethingElse ($filter, $data);
}
}
Now, basically, the idea behind this code should be clear: We want to
define a filter, there's an interface for that filter that any class may
define and there's a simple wrapper class for Closures for filters that
are supposed to be extremely simple. I don't think this example is
convoluted, one could easily imagine a similar design in the real word
(probably a bit more complex, but nevertheless).
The filter closure is now defined in the class Foo. Thus one would
assume that the closure is bound to the $this of the Foo object (once
created). But since that closure is passed to the constructor of the
Closure_Filter class, the $this would be rebound according to your
proposal. Thus, when invoking the method, the closure would now try to
access the ->min and ->max properties of Closure_Filter class - which is
clearly not the intention.
Of course, there are possibilites to circumvent that: 1) Copy the min
and max properties to local scope and bind them with use() to the
closure. Or 2) Don't store the closure directly inside a property of the
Closure_Filter class but in an array so $this doesn't get rebound. But
clearly, in my eyes, that is some kind of hackish workaround that really
sucks.
Also, with the implementation Dmitry and I wrote, it is very clear what
the semantics are $this: It is always bound at creation time and that's
it. Just to make a comparison:
// Variant 1:
return call_user_func ($this->closure, $value);
// Variant 2:
$closure = $this->closure;
return $closure ($value);
// Variant 3:
return $this->closure->__invoke ($value);
// Variant 4:
return $this->closure ($value);
Now, the original implementation:
Variant 1: $this bound to Foo class
Variant 2: $this bound to Foo class
Variant 3: $this bound to Foo class
Variant 4: doesn't work, because methods and properties have a
different namespace
Now, an implementation where all four variants bind to the
Closure_Filter class:
Variant 1: $this bound to Closure_Filter class
-> Inconsistent: call_user_func ($this->normal_method) will first
cause "undefined property" and then "invalid
callback" errors
Variant 2: $this bound to Closure_Filter class
-> Hmm, so this basically allows for the following code:
$closure = function ...;
$object->closure = $closure; // MAGIC happens!
$closure = $object->closure; // $closure changed - WTF?!
Variant 3: $this bound to Closure_Filter class
-> Ok, this really doesn't matter either way, using __invoke
directly looks a bit weird anyway.
Variant 4: $this bound to Closure_Filter class
-> Calling properties directly will certainly cause resolution
order headaches. Since that was the MAIN point on the list that
was discussed before my posting you will have to admit it at
least is not obvious what the resolution order should be.
Ok, you could say ONLY variant 4 should be added with dynamic and
temporary (i.e. call-time and for the duration of the call) rebinding of
the $this context. But that would also cause confusion: Why should $this
for the same property on some occasions a first object and on some
another? It doesn't make any sense!
Now, I would call this certainly a bigger mess than the inconsistency
wrt. Javascript. Since PHP is already different from Javascript (access
modifiers, "normal" object methods, different namespaces for methods and
properties), I don't really see the point in forcing something which
will create problems with the concepts PHP already has.
----------------------- snip ------------------
However, having said that, I may have a solution which will make
everybody (more or less at least) happy:
Since the main problem of re-binding closures to different objects is
the obscure magic just by assignment, why not make that magic "public"?
I.e.: Add a method bindTo() to the Closure class which returns an
identical copy of the closure (same bound variables etc.) with the
EXCEPTION that the new variables are already bound. This allows for
prototyping in the way that you want it BUT ensures that no silent magic
occurs that creates problems.
Example:
$func = function ($a, $b) {
return $a + $b + $this->bias;
};
$object->add = $func->bindTo ($object);
Additionally, a static method bind() could be added for direct assignments:
$object->add = Closure::bind ($object, function ($a, $b) {
return $a + $b + $this->bias;
});
The bindTo/bind functions could also check the current class scope and
ensure that the class scope of the newly bound closure has the same
access level to the object as the current class - that would enforce
access modifiers. Whether or not we want that is probably a matter of
debate.
Another idea which could be discussed (I'm not sure about it myself)
is that if you add variant 4 from above (i.e. calling object properties
like methods directly) - which you probably want to do if you want to
allow $this rebinding - and we have finally figured out some sane
resolution rules, one could also add a E_WARNING
message when the $this
pointer of the closure does not match the $this pointer of the object
for which the closure is called as a method. So, for example:
$closure = function () { return $this->value; };
$object->get1 = $closure;
$object->get2 = $closure->bindTo ($object);
call_user_func ($object->get1);
call_user_func ($object->get2);
$f = $object->get1; $f ();
$f = $object->get2; $f ();
$object->get1->__invoke ();
$object->get2->__invoke ();
// -> No warnings
// BECAUSE (!) this syntax makes it clear to the user that
// he is calling some closure, but not necessarily a closure
// bound to the specific object
$object->get1 ();
// -> Warning: Closure called directly but object scope does
// not match object closure is being called for!
// -> HOWEVER, execute the closure and DON'T rebind it
$object->get2 ();
// -> No warning (match)
The check should of course be intelligent enough to detect the
following correctly:
$this->foo = function () { ... };
$this->foo ();
// -> No warning (match, since assigned from inside the own object)
------------------ snip again --------------------
So, to sum up my posting:
-
Stance on direct method calling $foo->closure_property ()
- As long as you figure out some sane and consistent resolution rules: Fine with me.
-
Stance on automatic re-binding of $this
- Really big convoluted mess - PHP already does things differently from Javascript, has different concepts. => I'm against it.
-
Alternative proposal for making re-binding possible but making
sure this is done explicitly:- Figure out some sane resolution rules - Closure::bind and Closure->bindTo
-
Additional proposal to 3:
- Enforce class scope and thus access modifiers when using Closure::bind / Closure->bindTo
-
Additional propsoal to 3:
- Warning when using direct-calling and the closure is not bound to the object for which the closure is called.
Regards,
Christian
Hello Christian,
Sunday, January 18, 2009, 11:58:29 PM, you wrote:
Hi Marcus,
"Convoluted"? "Mess"? Are you kidding me? It's standard usage of access
handlers.It is a mess right now. You assign a closure to another method and get
access to the original owners private members. That is not only unexpected
and contradicting anything that any oyther language ever but but also
violates our very own basic ideas. That is what I call a mess.
You could also call Javascript's behaviour confusing. A closure is per
And I could say that what the two of you designed ofr PHP is not a design
but a very confusing incoherent implementation that is based on the lack
of being able to get support for something else in the underlying c
implementation. You took somethign very special and made it behave somewhat
different making it even more specific and different from anyone's
expectations, excluding people that understand the underlying c level
issues.
Either way, we should imo, simply disable $this for clusresm get and get
rid of any dynamic class binding mess or make them work. Doing a lot of
unexpected behavior is in my opinion very very bad.
definition a function that encloses its scope at creation time. E.g. if
you have the following (in Javascript, PHP, doesn't matter):
var foo;
var closure = function () {
alert (foo);
};
The current scope variable foo is inherited. The question is: Why
shouldn't the same also happen for the variable this? In the closures
implementatios Dmitry and I designed for PHP, it does. Admittedly, $this
is a special variable because it's implicitly available in normal
methods and thus we decided that for closures you don't need to do "use
($this)" either.
So the question is: Why does Javascript change the pointer to the this
variable upon calling a method? The answer is simple: Because there is
NO OTHER WAY to define object methods in Javascript. You always have
to use object.method = function () { }; or Something.prototype.method =
... in order to define a callable method. There is no other way. Because
of that, Javascript defines the behaviour with $this.
PHP, on the other hand, since it already does have a method for creating
normal class methods (simply define them in the class { } block), does
not need such a mechanism for normal OOP.
Also, implementing this in PHP may give quite some headaches. Take for
example the following code:
interface Some_Filter {
public function accept ($value);
}
class Closure_Filter implements Some_Filter {
private $closure;
public function __construct (Closure $closure) {
$this->closure = $closure;
}
public function accept ($value) {
// Or something similar, see below
return call_user_func ($this->closure, $value);
}
}
class Foo {
private $min, $max;
public function bar () {
$filter = new Closure_Filter (function ($value) {
return $value >= $this->min && $value <= $this->max;
});
$data = $something->doSomethingElse ($filter, $data);
}
}
Now, basically, the idea behind this code should be clear: We want to
define a filter, there's an interface for that filter that any class may
define and there's a simple wrapper class for Closures for filters that
are supposed to be extremely simple. I don't think this example is
convoluted, one could easily imagine a similar design in the real word
(probably a bit more complex, but nevertheless).
The filter closure is now defined in the class Foo. Thus one would
assume that the closure is bound to the $this of the Foo object (once
created). But since that closure is passed to the constructor of the
Closure_Filter class, the $this would be rebound according to your
proposal. Thus, when invoking the method, the closure would now try to
access the ->min and ->max properties of Closure_Filter class - which is
clearly not the intention.
Of course, there are possibilites to circumvent that: 1) Copy the min
and max properties to local scope and bind them with use() to the
closure. Or 2) Don't store the closure directly inside a property of the
Closure_Filter class but in an array so $this doesn't get rebound. But
clearly, in my eyes, that is some kind of hackish workaround that really
sucks.
Also, with the implementation Dmitry and I wrote, it is very clear what
the semantics are $this: It is always bound at creation time and that's
it. Just to make a comparison:
// Variant 1:
return call_user_func ($this->closure, $value);
// Variant 2:
$closure = $this->closure;
return $closure ($value);
// Variant 3:
return $this->closure->__invoke ($value);
// Variant 4:
return $this->closure ($value);
Now, the original implementation:
Variant 1: $this bound to Foo class
Variant 2: $this bound to Foo class
Variant 3: $this bound to Foo class
Variant 4: doesn't work, because methods and properties have a
different namespace
Now, an implementation where all four variants bind to the
Closure_Filter class:
Variant 1: $this bound to Closure_Filter class
-> Inconsistent: call_user_func ($this->normal_method) will first
cause "undefined property" and then "invalid
callback" errors
Variant 2: $this bound to Closure_Filter class
-> Hmm, so this basically allows for the following code:
$closure = function ...;
$object->closure = $closure; // MAGIC happens!
$closure = $object->closure; // $closure changed - WTF?!
Variant 3: $this bound to Closure_Filter class
-> Ok, this really doesn't matter either way, using __invoke
directly looks a bit weird anyway.
Variant 4: $this bound to Closure_Filter class
-> Calling properties directly will certainly cause resolution
order headaches. Since that was the MAIN point on the list that
was discussed before my posting you will have to admit it at
least is not obvious what the resolution order should be.
Ok, you could say ONLY variant 4 should be added with dynamic and
temporary (i.e. call-time and for the duration of the call) rebinding of
the $this context. But that would also cause confusion: Why should $this
for the same property on some occasions a first object and on some
another? It doesn't make any sense!
Now, I would call this certainly a bigger mess than the inconsistency
wrt. Javascript. Since PHP is already different from Javascript (access
modifiers, "normal" object methods, different namespaces for methods and
properties), I don't really see the point in forcing something which
will create problems with the concepts PHP already has.
----------------------- snip ------------------
However, having said that, I may have a solution which will make
everybody (more or less at least) happy:
Since the main problem of re-binding closures to different objects is
the obscure magic just by assignment, why not make that magic "public"?
I.e.: Add a method bindTo() to the Closure class which returns an
identical copy of the closure (same bound variables etc.) with the
EXCEPTION that the new variables are already bound. This allows for
prototyping in the way that you want it BUT ensures that no silent magic
occurs that creates problems.
Example:
$func = function ($a, $b) {
return $a + $b + $this->bias;
};
$object->add = $func->bindTo ($object);
Additionally, a static method bind() could be added for direct assignments:
$object->add = Closure::bind ($object, function ($a, $b) {
return $a + $b + $this->bias;
});
The bindTo/bind functions could also check the current class scope and
ensure that the class scope of the newly bound closure has the same
access level to the object as the current class - that would enforce
access modifiers. Whether or not we want that is probably a matter of
debate.
Another idea which could be discussed (I'm not sure about it myself)
is that if you add variant 4 from above (i.e. calling object properties
like methods directly) - which you probably want to do if you want to
allow $this rebinding - and we have finally figured out some sane
resolution rules, one could also add aE_WARNING
message when the $this
pointer of the closure does not match the $this pointer of the object
for which the closure is called as a method. So, for example:
$closure = function () { return $this->value; };
$object->get1 = $closure;
$object->get2 = $closure->bindTo ($object);
call_user_func ($object->get1);
call_user_func ($object->get2);
$f = $object->get1; $f ();
$f = $object->get2; $f ();
$object->get1->__invoke ();
$object->get2->__invoke ();
// -> No warnings
// BECAUSE (!) this syntax makes it clear to the user that
// he is calling some closure, but not necessarily a closure
// bound to the specific object
$object->get1 ();
// -> Warning: Closure called directly but object scope does
// not match object closure is being called for!
// -> HOWEVER, execute the closure and DON'T rebind it
$object->get2 ();
// -> No warning (match)
The check should of course be intelligent enough to detect the
following correctly:
$this->foo = function () { ... };
$this->foo ();
// -> No warning (match, since assigned from inside the own object)
------------------ snip again --------------------
So, to sum up my posting:
- Stance on direct method calling $foo->closure_property ()
- As long as you figure out some sane and consistent resolution rules: Fine with me.
- Stance on automatic re-binding of $this
- Really big convoluted mess - PHP already does things differently from Javascript, has different concepts. => I'm against it.
- Alternative proposal for making re-binding possible but making
sure this is done explicitly:
- Figure out some sane resolution rules - Closure::bind and Closure->bindTo
- Additional proposal to 3:
- Enforce class scope and thus access modifiers when using Closure::bind / Closure->bindTo
- Additional propsoal to 3:
- Warning when using direct-calling and the closure is not bound to the object for which the closure is called.
Regards,
Christian
Best regards,
Marcus
Hi!
It is a mess right now. You assign a closure to another method and get
access to the original owners private members. That is not only unexpected
Could you give a code example? I'm not sure I understand what you mean
by access (or "assign closure to a method" for that matter) - if access
provided by the closure, it's exactly the intent of the closure. If some
other access, then please explain which one.
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
Hello Stanislav,
Monday, January 19, 2009, 9:32:09 AM, you wrote:
Hi!
It is a mess right now. You assign a closure to another method and get
access to the original owners private members. That is not only unexpected
Could you give a code example? I'm not sure I understand what you mean
by access (or "assign closure to a method" for that matter) - if access
provided by the closure, it's exactly the intent of the closure. If some
other access, then please explain which one.
class Foo {
private $myVeryOwnData;
function bla {
$this->bigMEss = function () { return $this->myVeryOwnData; }
}
}
$o = new Foo;
$f = $o->bla;
echo $f();
class Bar {
function baz{} {
$this->xyz = $o->bla; // or $f;
// I am very sorry but I would expect the closure to rebind $this as
// $this always points to the bound object. And how is this different?
// If the different is that the closure is already bound and that is
// what you want, then we need ways to do just that. And not inventing
// new ways to deal with $this.
}
}
$onother = new stdClass;
$another->blabla = $f; // yet again a very unexpected $this.
If this really was to be bound to a $this at the creation, then we'd put it
into the scope binding list and not have it be handled implicitly. But then
we cannot do that with the current c level api. So instead you created
brand new rules, which unfortunately is the PHP way.
So how would I copy a closure instead of rebinding it?
I would use PHP's current established semantics for that. That is I'd do:
$this->closure = clone $closure;
$this->closure = $closure->copy();
How would I prevent automatic $this binding?
$closure = static function()....
--
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
Best regards,
Marcus
Hi!
class Foo {
private $myVeryOwnData;
function bla {
$this->bigMEss = function () { return $this->myVeryOwnData; }
}
}
This is not a "mess" - this is exactly what closures are for. If you
don't want this function to be able to access private variables - do not
put code that accesses private variables there! That's like saying this
code is a "mess":
class Foo {
private $myVeryOwnData;
public function getData() {
return $this->myVeryOwnData;
}
}
because you can do this:
$a = new Foo();
echo $a->getData();
Oh horror!! It accessed private data!! Because you explicitly told it to
access it, yes. What did you expect if you provide public method to
access private data?
// I am very sorry but I would expect the closure to rebind $this as // $this always points to the bound object. And how is this different?
I am very sorry but your expectation is wrong. There's no reason for
closures to magically rebind, and if some particular language, due to
limitations of its design, has this weird WTF inside, it's a problem
that we don't have to carry on into PHP. If you created a closure, you
got context. That's the whole point of closure. $this is part of the
context. If for some reason you need WTF-closures that keep half of the
context from where they were created and half from where they were
called, you may create it, but regular closures shouldn't do that.
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
Hi,
maybe an IRC meeting is the easiest way to come to an agreement. How
about tomorrow evening 21:00 CEST in #php.closures on freenode?
regards.
Lukas
Hi,
maybe an IRC meeting is the easiest way to come to an agreement. How
about tomorrow evening 21:00 CEST in #php.closures on freenode?
Just for clarification: I assume you mean Wednesday, January 21st, 19:00
UTC (CEST == UTC+2) and thus 20:00 CET? Would be fine with me.
Regards,
Christian
Am 20.01.2009 um 18:41 schrieb Christian Seiler:
Hi,
maybe an IRC meeting is the easiest way to come to an agreement. How
about tomorrow evening 21:00 CEST in #php.closures on freenode?Just for clarification: I assume you mean Wednesday, January 21st,
19:00
UTC (CEST == UTC+2) and thus 20:00 CET? Would be fine with me.
I'm pretty confident he didn't mean summer time, so it's likely 21:00
CET or 20:00 UTC.
Am 20.01.2009 um 18:41 schrieb Christian Seiler:
Hi,
maybe an IRC meeting is the easiest way to come to an agreement. How
about tomorrow evening 21:00 CEST in #php.closures on freenode?Just for clarification: I assume you mean Wednesday, January 21st,
19:00
UTC (CEST == UTC+2) and thus 20:00 CET? Would be fine with me.I'm pretty confident he didn't mean summer time, so it's likely
21:00 CET or 20:00 UTC.
actually i did not think and it was just a random suggestion :)
since Christian wasn't forceful enough to settle things on a time,
lets make it 21:00 CET or 20:00 UTC, tonight.
the channel is already open and people are ideling in there. however i
would suggest that most of the talking should be done by the key
people involved (marcus, stas, dmitry, christian, stan). other people
are of course also invited to participate, but in order to let the
discussion on irc to not go totally crazy it might be better that
people pick their favorite of the 5 and just PM them with their
thoughts, so that they can then relay them. for this purpose people
will need a registered account on freenode IIRC. but there are no
official rules .. so when in doubt common sense/courtesy rules :)
regards,
Lukas
PS: i will probably not participate .. though i will be idleing in the
channel.
Hi everybody,
We had that chat that Lukas announced yesterday and I promised Lukas to
sum up the results of that chat.
Problem was: There were only four people really there: Stas, Lukas,
David and me. Lukas was interested in getting results in order to be
able to release PHP 5.3 beta 1, Stas and I basically agreed on
everything and unfortunately David didn't stay long enough in order to
actually have a real discussion.
I'm not assigning blame here for not coming to anyone, I just wanted to
make clear that we actually couldn't reach a consensus - or well,
rather, Stas and I could but I don't think that's the way it should be.
However, Lukas wants to release 5.3 beta 1 and I can really understand
him that he doesn't want to drag this out infinitely.
I've written up the results of the discussion I had with Stas in the
Wiki for everyone to read. I hope there's enough structure in it to make
the main points clear. Please read it thoroughly before replying on-list
in order to actually have a constructive dialogue.
Here it is: http://wiki.php.net/rfc/closures/object-extension
In order to be able to get beta 1 out as soon as possible, I propose the
following:
-
Remove $obj->closure_method() call syntax for beta1. Make sure
rebinding of $this is not in beta1. Freeze tomorrow (Friday, Jan 23rd). -
Discuss my proposal and work out the details for adding manual
rebining of $this in closures. Add that post-beta1 or if we don't get
there in time at least post-5.3.0.
Alternatively, if you really don't like it at all the current way and
can't imagine ever finding a consensus on my new proposal with
bind()/bindTo(), then I propose the following:
-
Remove $obj->closure_method() call syntax for beta1. Make sure
rebinding of $this is not in beta1. Freeze tomorrow (Friday, Jan 23rd).
This is exactly the state of alpha1 up to alpha3 so we won't actually be
entering new territory with that. -
Remove OOP support from closures post-beta1 (i.e. for beta2) in
order to leave all options open. Thus, closures will always be normal
functions without access to $this or having a class scope and that may
later be added in the fashion that we agree upon once we do that. (It
will certainly still be possible to define closures inside class methods
then, they just won't know about that.)
We definitely won't have to go so far as Johannes and remove closures
entirely. Worst-case scenario, we remove OOP interaction and consider
the proper way for that later.
On the other hand, I really would dislike stripping closures of OOP
support completely - at least not after it was in there for about six
months (!) and nobody really complained about that until now,
immediately before beta1...
So, IMMEDIATE question: Is it ok for you to leave closure OOP support in
beta1 on the level of alpha3 in order to allow Lukas and Johannes to
finally release it? Even if you don't agree with my compromise proposal,
please consider that in the worst case we can still remove all OOP
support from closures after beta1.
Regards,
Christian
I like the proposal. I got near the same idea few hours ago :)
The only difference is in binding/creation. You suggest
$obj->method2 = Closure::bind ($obj, function () { ... });
and I would prefer something like
create_prototype_method($obj, "method2", function () { ... });
But it's not a big deal. I can accept both.
Thanks. Dmitry.
Christian Seiler wrote:
Hi everybody,
We had that chat that Lukas announced yesterday and I promised Lukas to
sum up the results of that chat.Problem was: There were only four people really there: Stas, Lukas,
David and me. Lukas was interested in getting results in order to be
able to release PHP 5.3 beta 1, Stas and I basically agreed on
everything and unfortunately David didn't stay long enough in order to
actually have a real discussion.I'm not assigning blame here for not coming to anyone, I just wanted to
make clear that we actually couldn't reach a consensus - or well,
rather, Stas and I could but I don't think that's the way it should be.
However, Lukas wants to release 5.3 beta 1 and I can really understand
him that he doesn't want to drag this out infinitely.I've written up the results of the discussion I had with Stas in the
Wiki for everyone to read. I hope there's enough structure in it to make
the main points clear. Please read it thoroughly before replying on-list
in order to actually have a constructive dialogue.Here it is: http://wiki.php.net/rfc/closures/object-extension
In order to be able to get beta 1 out as soon as possible, I propose the
following:
Remove $obj->closure_method() call syntax for beta1. Make sure
rebinding of $this is not in beta1. Freeze tomorrow (Friday, Jan 23rd).Discuss my proposal and work out the details for adding manual
rebining of $this in closures. Add that post-beta1 or if we don't get
there in time at least post-5.3.0.Alternatively, if you really don't like it at all the current way and
can't imagine ever finding a consensus on my new proposal with
bind()/bindTo(), then I propose the following:
Remove $obj->closure_method() call syntax for beta1. Make sure
rebinding of $this is not in beta1. Freeze tomorrow (Friday, Jan 23rd).
This is exactly the state of alpha1 up to alpha3 so we won't actually be
entering new territory with that.Remove OOP support from closures post-beta1 (i.e. for beta2) in
order to leave all options open. Thus, closures will always be normal
functions without access to $this or having a class scope and that may
later be added in the fashion that we agree upon once we do that. (It
will certainly still be possible to define closures inside class methods
then, they just won't know about that.)We definitely won't have to go so far as Johannes and remove closures
entirely. Worst-case scenario, we remove OOP interaction and consider
the proper way for that later.On the other hand, I really would dislike stripping closures of OOP
support completely - at least not after it was in there for about six
months (!) and nobody really complained about that until now,
immediately before beta1...So, IMMEDIATE question: Is it ok for you to leave closure OOP support in
beta1 on the level of alpha3 in order to allow Lukas and Johannes to
finally release it? Even if you don't agree with my compromise proposal,
please consider that in the worst case we can still remove all OOP
support from closures after beta1.Regards,
Christian
Hi Dmitry,
The only difference is in binding/creation. You suggest
$obj->method2 = Closure::bind ($obj, function () { ... });
and I would prefer something like
create_prototype_method($obj, "method2", function () { ... });
I prefer a static method of the Closure class because it does not
pollute the global function namespace - and also it is far mor OOP-ish
and we are doing this for closure OOP support.
Regards,
Christian
Hi again,
ok, I just verified that the current PHP 5.3 CVS has the same behaviour
as PHP 5.3 alpha 3 (which is the original design). So basically, I'd
suggest the following:
-
Feature freeze as Lukas and Johannes had planned tomorrow with no
more changes wrt. closures for beta1, then release beta1. -
For post-beta1: Discuss which of the following options we want to
take:a) My Closure::bind() compromise
b) Leave as is in order to add Closure::bind() later (5.3.1, 5.4,
6.0) when we've discussed all the details.
c) Drop $this/OOP support from closures for beta2 in order to be
able to discuss this properly for a later version.
Regards,
Christian
hi,
Hi again,
ok, I just verified that the current PHP 5.3 CVS has the same behaviour
as PHP 5.3 alpha 3 (which is the original design). So basically, I'd
suggest the following:
Feature freeze as Lukas and Johannes had planned tomorrow with no
more changes wrt. closures for beta1, then release beta1.For post-beta1: Discuss which of the following options we want to
take:a) My Closure::bind() compromise
b) Leave as is in order to add Closure::bind() later (5.3.1, 5.4,
6.0) when we've discussed all the details.
c) Drop $this/OOP support from closures for beta2 in order to be
able to discuss this properly for a later version.
If we know limitations, issues or design problems now and they can be
fixed in a relatively short delay (within a couple of weeks), then I
do not see why we should release 5.3.0 with them. We are in a test
phase and that means that we will have to change one thing or another,
even after beta1. That's what I meant in my previous posts.
so in short:
-1 for option b)
+1 to solve the issue for 5.3.0-final before beta1.
Cheers,
Pierre
hi,
On Thu, Jan 22, 2009 at 7:36 PM, Christian Seiler chris_se@gmx.net
wrote:Hi again,
ok, I just verified that the current PHP 5.3 CVS has the same
behaviour
as PHP 5.3 alpha 3 (which is the original design). So basically, I'd
suggest the following:
Feature freeze as Lukas and Johannes had planned tomorrow with no
more changes wrt. closures for beta1, then release beta1.For post-beta1: Discuss which of the following options we want to
take:a) My Closure::bind() compromise
b) Leave as is in order to add Closure::bind() later (5.3.1, 5.4,
6.0) when we've discussed all the details.
c) Drop $this/OOP support from closures for beta2 in order to be
able to discuss this properly for a later version.If we know limitations, issues or design problems now and they can be
fixed in a relatively short delay (within a couple of weeks), then I
do not see why we should release 5.3.0 with them. We are in a test
phase and that means that we will have to change one thing or another,
even after beta1. That's what I meant in my previous posts.
right .. we are in a test phase .. not experiment phase. the time for
experiments is over. we have enough features for a couple releases. so
if we find that some feature is not done yet .. or that we might need
to break BC later if we release the feature now, then we should strip
the feature until we do not foresee possible BC breaks and move on.
if we keep delaying a few weeks here and there, we will do PHP a
greater disservice. as johannes has pointed out we have delayed some
features because we wanted to focus on getting 5.3 out. if necessary
we can always have a PHP 5.4 just as well.
regards,
Lukas Kahwe Smith
mls@pooteeweet.org
hi,
On Thu, Jan 22, 2009 at 7:36 PM, Christian Seiler chris_se@gmx.net
wrote:Hi again,
ok, I just verified that the current PHP 5.3 CVS has the same behaviour
as PHP 5.3 alpha 3 (which is the original design). So basically, I'd
suggest the following:
Feature freeze as Lukas and Johannes had planned tomorrow with no
more changes wrt. closures for beta1, then release beta1.For post-beta1: Discuss which of the following options we want to
take:a) My Closure::bind() compromise
b) Leave as is in order to add Closure::bind() later (5.3.1, 5.4,
6.0) when we've discussed all the details.
c) Drop $this/OOP support from closures for beta2 in order to be
able to discuss this properly for a later version.If we know limitations, issues or design problems now and they can be
fixed in a relatively short delay (within a couple of weeks), then I
do not see why we should release 5.3.0 with them. We are in a test
phase and that means that we will have to change one thing or another,
even after beta1. That's what I meant in my previous posts.right .. we are in a test phase .. not experiment phase. the time for
experiments is over. we have enough features for a couple releases. so if we
find that some feature is not done yet .. or that we might need to break BC
later if we release the feature now, then we should strip the feature until
we do not foresee possible BC breaks and move on.if we keep delaying a few weeks here and there, we will do PHP a greater
disservice. as johannes has pointed out we have delayed some features
because we wanted to focus on getting 5.3 out. if necessary we can always
have a PHP 5.4 just as well.
I'm talking about the features we already have, not about adding more
major features. If one of the new features is not complete and we know
it, why do you want to go ahead then? I can't find any good reason.
--
Pierre
Hi!
I'm talking about the features we already have, not about adding more
major features. If one of the new features is not complete and we know
it, why do you want to go ahead then? I can't find any good reason.
You can argue every feature is not complete because there's always
something somebody wants to add or change. However the good reason it is
complete enough to be useful, and if we start right now to discuss yet
another set of new features we won't have release until 2010 or 2011 -
meaning new features we already have would be useless, since nobody
could use them before we actually release them. What happen to
"release often", folks? Why you must stuff every single idea anybody has
in this particular version?
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
I'm talking about the features we already have, not about adding more
major features. If one of the new features is not complete and we know
it, why do you want to go ahead then? I can't find any good reason.
because it means all the other features have to wait to be released.
this includes all the features that will be released as part of
5.3 ... and all the features that are waiting for after 5.3.
its really time we think release oriented .. not because our money
depends on it .. but simply because its the right thing to do to at
this point.
regards,
Lukas Kahwe Smith
mls@pooteeweet.org
I'm talking about the features we already have, not about adding more
major features. If one of the new features is not complete and we know
it, why do you want to go ahead then? I can't find any good reason.because it means all the other features have to wait to be released. this
includes all the features that will be released as part of 5.3 ... and all
the features that are waiting for after 5.3.its really time we think release oriented .. not because our money depends
on it .. but simply because its the right thing to do to at this point.
Exactly, release oriented.
If a feature was planed for a release, it has to be done in the best
possible way, that means solving known issue as far as it is possible.
In this case, both requirements are present. It was planed and issues
have been identified and there is on going discussions about the best
solution. That's why I do not (and will surely never) understand why
we should go ahead with 5.3.0 and think about fixing that after. That
makes no sense to me.
And for anything new coming around and not planed for 5.3.0, I fully
agree with you.
Cheers,
Pierre
On Thursday 22 January 2009 8:21:28 am Christian Seiler wrote:
Hi everybody,
We had that chat that Lukas announced yesterday and I promised Lukas to
sum up the results of that chat.Problem was: There were only four people really there: Stas, Lukas,
David and me. Lukas was interested in getting results in order to be
able to release PHP 5.3 beta 1, Stas and I basically agreed on
everything and unfortunately David didn't stay long enough in order to
actually have a real discussion.I'm not assigning blame here for not coming to anyone, I just wanted to
make clear that we actually couldn't reach a consensus - or well,
rather, Stas and I could but I don't think that's the way it should be.
However, Lukas wants to release 5.3 beta 1 and I can really understand
him that he doesn't want to drag this out infinitely.I've written up the results of the discussion I had with Stas in the
Wiki for everyone to read. I hope there's enough structure in it to make
the main points clear. Please read it thoroughly before replying on-list
in order to actually have a constructive dialogue.Here it is: http://wiki.php.net/rfc/closures/object-extension
IMO, a very large part of the problem is the implicit binding of $this when a
closure happens to be defined inside a method of a class. Every other variable
that the closure inherits needs to be defined explicitly; that not only helps
the parser but helps human readers. Having $this be magically bound seems
like a cause for confusion later on down the road.
That said, I very much like the proposal given in the RFC. Rebinding of a
closure should be an explicit operation that a future reader of the code can
visually see and therefore know about. That removes any potential WTFs when
$this is suddenly $that even though you didn't mean for it to be.
Regarding the details, wherein lives the devil:
-
If a closure is bound to an object and does NOT make use of $this, do we
still want to throw a warning if it is called? In that case the binding is
irrelevant anyway. (That goes back to the point about an implicit initial
bind, which I am still not convinced is wise.) -
IMO, once a closure has been bound to an object, its $this should have all
the same access as a method defined literally on that object's class. That is,
it should have private/protected access the same as a "normal" method. -
I am a little unclear on what the expected cloning behavior is. Do closures
have a clone method? Take the following example:
class A {
function getClosure() {
$var = new Foo();
return function() use ($var) {
return $var->something;
}
}
}
$a = new A;
$o = new stdClass();
$closure = $a->getClosure();
$o->foo = $closure->bindTo($o);
Now, are $closure->var and $o->foo->var the same object or no? I'm assuming
they are from the RFC, but then how do you implement a deep clone of the
closure on binding if you need to?
--
Larry Garfield
larry@garfieldtech.com
Hi!
maybe an IRC meeting is the easiest way to come to an agreement. How
about tomorrow evening 21:00 CEST in #php.closures on freenode?
ok
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
Hi Marcus,
I've just got an idea how prototyping may be implemented without magic
you solution had. We can use a special function
function attach_method($object, $name, $closure);
This function should clone the $closure bind $this of clone of $closure
to $object and assign it into $object->name (the original $closure
should be unchanged).
However it won't make possible to call it as $object->name() anyway.
Also, it's a question what should attach_method() do if $closure already
has $this (rebind it or throw an exception).
Thanks. Dmitry.
Am 13.01.2009 um 22:58 schrieb Marcus Boerger:
- A closure assigned to a property can be called directly rather
than just by a temporary variable:
$obj->property = function() {}
$obj->property(); // valid callThis will get us into trouble IMHO, as it would not behave too well
with
__get/__set and __call. I.e. if we have $foo->nosuchproperty() -
should we call __get or __call? So far variables implemented through
__get behave exactly like ones implemented through definition - but I
don't understand how to reconcile it with this.Why would you call __get() here? Becasue I did that by mistake in my
very
first mail? You clearly have a function call and thus you only go for
__call(). As of today you can already do:function __call($name, $args) {
if ($this->properties[$name] instanceof 'Closure') {
return call_user_func_array($this->property[$name], $args);
}
}Now we already have callable properties - directly callable.
- method
- __call()
- property
- __get()
that would be a reasonable calling order, or:
- method
- property
- __call()
- __get()
In any case, a "real" method (existing one or __call() overload)
should have precedence over a closure in a property.
Wouldn't that work?
- David
Hi,
Ok, my proposal to drop OO support from Closures was exactly embraced.
All I want you guys to think about is how you want to deal with this
in terms of the PHP 5.3 release. If no decision is found and
implemented over the weekend, this topic will essentially cement the
need for an additional beta .. or at worse staying at alpha.
I am playing a frisbee tournament this weekend, so I hope by Sunday
evening there is a decision on how to proceed in regards to Closures/
Prototypes. Depending on what that decision is, we will figure out
some schedule.
The current plan would see a freeze on Monday, giving us Tuesday to do
build fixing, Wednesday to package and Thursday to release.
regards,
Lukas