What was the conclusion on Wez' patch from march [1]? The discussion
seemed to steer a bit off, on the discussion of scoping rules, but is
there any reason not to implement Wez' patch in HEAD?
Even if it doesn't entirely replace create_function, it would be nice
to have as a compile-time alternative.
--
troels
I think the problem there is that this syntax wouldn't support external
variables, and without them there's not much difference between that and
create_function.
troels knak-nielsen wrote:
What was the conclusion on Wez' patch from march [1]? The discussion
seemed to steer a bit off, on the discussion of scoping rules, but is
there any reason not to implement Wez' patch in HEAD?
Even if it doesn't entirely replace create_function, it would be nice
to have as a compile-time alternative.
--
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
Actually it's the opposite. With create_function, you can bind
variables, by marshalling them to a string and embed them in the
function-definition. With a static syntax, this isn't possible.
However, this is only a problem, when you need to bind variables,
which isn't always the case and even then, there are other ways to
bind variables in runtime.
So it's different from create_function, in that it doesn't allow
binding of variables, but I don't see why that should stop us from
implementing, considering the benefits it gives in readability and
debugging.
--
troels
I think the problem there is that this syntax wouldn't support external
variables, and without them there's not much difference between that and
create_function.troels knak-nielsen wrote:
What was the conclusion on Wez' patch from march [1]? The discussion
seemed to steer a bit off, on the discussion of scoping rules, but is
there any reason not to implement Wez' patch in HEAD?
Even if it doesn't entirely replace create_function, it would be nice
to have as a compile-time alternative.--
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
Actually it's the opposite. With create_function, you can bind
variables, by marshalling them to a string and embed them in the
That's not binding. But the problem is, seeing this, one expects
closure. And it's no closure.
So it's different from create_function, in that it doesn't allow
binding of variables, but I don't see why that should stop us from
implementing, considering the benefits it gives in readability and
debugging.
I don't see how it'd help anything in debugging.
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
Hi,
Am Sonntag, den 16.12.2007, 11:56 -0800 schrieb Stanislav Malyshev:
Actually it's the opposite. With create_function, you can bind
variables, by marshalling them to a string and embed them in theThat's not binding. But the problem is, seeing this, one expects
closure. And it's no closure.
Maybe deprecating create_function()
and allowing something like that
would be a way to go?
$func = function($arg) {
return $arg;
}
As it is compile time, the issue with Op-Code caches would/should go
away.
cu, Lars
Lars Strojny
Senior Software Developer
Neu.de GmbH
Wesselinger Strasse 28
50999 Köln
Eingetragen beim Amtsgericht Köln, HRB 61021
Geschäftsführer: Sven Jan Arndt, Christian Dornhoff
Telefon: 02236-48010-21
Fax: 02236-48010-01
Mobil: 0177-3058654
E-Mail: lstrojny@neu.de
Jabber: lars@mabber.com
Hi,
Am Montag, den 17.12.2007, 08:44 +0100 schrieb Lars Strojny:
[...]
Maybe deprecating
create_function()
and allowing something like that
would be a way to go?
$func = function($arg) {
return $arg;
}
What a stupid and useless remark. Ignore me, sorry.
cu, Lars
Actually it's the opposite. With create_function, you can bind
variables, by marshalling them to a string and embed them in theThat's not binding. But the problem is, seeing this, one expects
closure. And it's no closure.
Well, documentation can include the following written using large font:
Warning: This is not a closure. PHP doesn't have native means for
nested contexts. This construct is just another way of creating usual
function during compile-time. (for creating functions in run-time see
create_function)
--
Alexey Zakhlestin
http://blog.milkfarmsoft.com/
Well, documentation can include the following written using large font:
Warning: This is not a closure. PHP doesn't have native means for
nested contexts. This construct is just another way of creating usual
function during compile-time. (for creating functions in run-time see
create_function)
It doesn't help. Nobody reads the docs if he thinks he understands how
it should work anyway. Having disclaimer in the manual doesn't do much -
we are not worried about being sued, we are worried about feature
looking one way and actually working other way.
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
I don't see how it'd help anything in debugging.
Presumably, a stack trace would now contain file and line number.
That's not binding. But the problem is, seeing this, one expects
closure. And it's no closure.
One might expect that, coming from a language with static scope. Then
again, one would also expect global variables in that event. I don't
really see this as a huge show stopper. My experience from Javascript
(Which probably is the language, that most PHP'ers are likeable to
have met anonymous functions in) is, that lesser experienced
programmers, are often surprised by static scope. Even if they have
used Javascript for a while, they have just assumed, that it was
global variables. I think the majority of the users won't miss the
feature and those who do, will be able to appreciate why it isn't
there.
--
troels
Hello,
Reading the prior discussion, I think either $_SCOPE['x'] or the
lexical $x syntax is fine for accessing local variables in the
enclosing scope. But closures also should also support $this and
static:: when the closure is defined in a method.
I think a solution for closures should add a new variable type. That
way, the code can compiled at compile time, while the enclosing
context (local variables plus $this for methods) can be captured at
run time in the new variable type. Also, when the closure value is
garbage collected, the references to the enclosing scope and object
instance can also be freed.
Additionally, the callback psuedo-type could be promoted to the new
type, with the string or array representation converted when
required. It wouldn't be necessary, but a callback literal along the
lines of Array() could also be added to make the conversion
unnecessary. I find the following to be easier to understand:
set_error_handler(Callback("foo"));
Than
set_error_handler("foo");
The current syntax would still work because of automatic type
conversion.
Best Regards,
Jeff
Let me see if I'm understanding you correctly, because that sounds like the
line I'd been thinking along.
function baz() {
$var = 5;
$foo = function($a, $b) {
lexical $var;
return $a + $b + $var;
}
return $foo;
}
$func = baz();
function bar($func) {
return $func(2, 3);
}
echo bar($foo); // prints 10
Right? Would it be possible to push the heavy lifting onto the compiler
rather than runtime in the above case?
I'm assuming that making the function above GC-able would be a herculean task
at this point, based on previous comments, but I do not actually know myself.
Hello,
Reading the prior discussion, I think either $_SCOPE['x'] or the
lexical $x syntax is fine for accessing local variables in the
enclosing scope. But closures also should also support $this and
static:: when the closure is defined in a method.I think a solution for closures should add a new variable type. That
way, the code can compiled at compile time, while the enclosing
context (local variables plus $this for methods) can be captured at
run time in the new variable type. Also, when the closure value is
garbage collected, the references to the enclosing scope and object
instance can also be freed.Additionally, the callback psuedo-type could be promoted to the new
type, with the string or array representation converted when
required. It wouldn't be necessary, but a callback literal along the
lines of Array() could also be added to make the conversion
unnecessary. I find the following to be easier to understand:set_error_handler(Callback("foo"));
Than
set_error_handler("foo");
The current syntax would still work because of automatic type
conversion.Best Regards,
Jeff
--
Larry Garfield AIM: LOLG42
larry@garfieldtech.com ICQ: 6817012
"If nature has made any one thing less susceptible than all others of
exclusive property, it is the action of the thinking power called an idea,
which an individual may exclusively possess as long as he keeps it to
himself; but the moment it is divulged, it forces itself into the possession
of every one, and the receiver cannot dispossess himself of it." -- Thomas
Jefferson
I'm assuming that making the function above GC-able would be a
herculean task
at this point, based on previous comments, but I do not actually
know myself.
Hi Larry,
Let me use a different example than yours.
function getAdder($x) {
return function ($y) {
lexical $x;
return $x + $y;
}
}
$plusFive = getAdder(5);
$plusTen = getAdder(10);
echo $plusFive(4); // 9
echo $plusTen(7); // 17
If the closure definition (and thus getAddr) returns a string
representing an anonymous function name, then there is no where to
hold the enclosing variable $x and no way to know when to free it.
Notice that in this example, we have two different enclosing contexts,
one where $x is 5 and one where $x is 10.
If we introduce a new variable type, a new kind of zval, then
internally, that variable can hold:
- A reference to the local variables of getAddr. (here $x)
- A reference to $this if the closure is declared in a method
- A reference to the opcodes of the compiled anonymous function.
The opcodes for the closure would be compiled and squirreled away when
the script is compiled.
When getAddr is executed, a zval would be created that holds the
reference to the enclosing locals and a reference to the proper
opcodes. Note that $plusFive and $plusTen would hold two different
zvals, one for each invocation of getAddr, but that both would refer
to the same opcodes structure.
When a codeblock zval is executed, such as at $plusFive(4), the
enclosing context can be passed to the anonymous function function to
make the lexical $x statement bind to the proper variable.
When the zval in $plusFive reaches zero references, it can
subsequently free its references to the local variables of getAddr.
As far as I can see, no herculean garbage collection or reference
counting is required.
Adding a new kind of zval? Perhaps that's Herculean. I don't know.
I hope I've gotten my zval terminology right.
Best Regards,
Jeff
I'm assuming that making the function above GC-able would be a
herculean task
at this point, based on previous comments, but I do not actually
know myself.Hi Larry,
Let me use a different example than yours.
function getAdder($x) {
return function ($y) {
lexical $x;
return $x + $y;
}
}$plusFive = getAdder(5);
$plusTen = getAdder(10);echo $plusFive(4); // 9
echo $plusTen(7); // 17If the closure definition (and thus getAddr) returns a string
representing an anonymous function name, then there is no where to
hold the enclosing variable $x and no way to know when to free it.
Notice that in this example, we have two different enclosing contexts,
one where $x is 5 and one where $x is 10.If we introduce a new variable type, a new kind of zval, then
internally, that variable can hold:
- A reference to the local variables of getAddr. (here $x)
- A reference to $this if the closure is declared in a method
- A reference to the opcodes of the compiled anonymous function.
The opcodes for the closure would be compiled and squirreled away when
the script is compiled.When getAddr is executed, a zval would be created that holds the
reference to the enclosing locals and a reference to the proper
opcodes. Note that $plusFive and $plusTen would hold two different
zvals, one for each invocation of getAddr, but that both would refer
to the same opcodes structure.
So the compiled code is then single-sourced, much the same way that a class is
single sourced and objects simply contain references to it. The Objects can
be GCed whenever, but the class opcodes remain for the life of the script.
When a codeblock zval is executed, such as at $plusFive(4), the
enclosing context can be passed to the anonymous function function to
make the lexical $x statement bind to the proper variable.When the zval in $plusFive reaches zero references, it can
subsequently free its references to the local variables of getAddr.As far as I can see, no herculean garbage collection or reference
counting is required.
By separating the opcodes from the zval, you've side-stepped the need to do
so. That means declaring a dynamic function (or lambda, or whatever we end
up calling this thing) inside a loop is no more expensive than instantiating
a class inside of a loop. I'm glad you know more about engine internals than
I do. :-)
Adding a new kind of zval? Perhaps that's Herculean. I don't know.
It sounds (to the layman) like it could/should be modeled on the way
objects/classes are implemented in PHP 5, as "fancy references". (I believe
Sara Golemon had an article a while back that explained them that way. Sara,
my apologies if I am misquoting you.) Whether that's a "new type of zval" or
not I do not know.
I hope I've gotten my zval terminology right.
Better than mine would be. :-) And it seems to solve most of the
implementation concerns that were raised earlier, at least at an abstract
level. The catch would be that I don't know the performance impact of making
the compiler find and define these sorts of functions. Can someone with more
engine experience weigh in if the above is workable?
--
Larry Garfield AIM: LOLG42
larry@garfieldtech.com ICQ: 6817012
"If nature has made any one thing less susceptible than all others of
exclusive property, it is the action of the thinking power called an idea,
which an individual may exclusively possess as long as he keeps it to
himself; but the moment it is divulged, it forces itself into the possession
of every one, and the receiver cannot dispossess himself of it." -- Thomas
Jefferson
Reading the prior discussion, I think either $_SCOPE['x'] or the
lexical $x syntax is fine for accessing local variables in the
enclosing scope. But closures also should also support $this and
static:: when the closure is defined in a method.
While I realise that the possibilities of static scope in PHP, are
intriguing, I really think it's a whole different discussion. Related,
yes. Interesting, yes. But let's keep it separate, to avoid derailing
the whole discussion.
While we are at it, what's wrong with knowing the name? I can see why
closure can be fun when you can dynamically use outer-scope variables.
But when you can't, what exactly prevents one from just doing the function?
If the code block, which needs to create the function, could be run
more than once, it would try to define the function twice, leading to
a fatal error. Yes, I could guard it with is_function_defined(), but
that wouldn't be pretty. Technically speaking, you're right, but I
think aesthetics does have some weight. Or should. It's also my main
gripe against create_function()
in the first place.
First, about 100% on the first encounter for any user ever seeing
closures in any other language. Second, all the confusion possible, like
So how big a part of PHP's userbase is that? I'm guessing, it's small.
closures in any other language. Second, all the confusion possible, like
"oh, closures! cool! let me do this and that! what, I can't use
variables?! Are you kidding me?! WTF is it useful for?!"
True, but the people who will anguish over lack of closures, are
already tearing their hair out over create_function()
.
I'm not sure I understand what you mean by "static scope" and "dynamic
scope", ...
Static scope is a synonym for lexical scope.
scope", but anyway thing that looks like closure but works like regular
function definition that isn't - is not a very good idea, IMO.
Yes, that's the primary problem with the patch. I don't think it's as
big an issue, as you do and given the alternatives, I'm willing to
make that sacrifice. How's other peoples take on this point?
--
troels
So how big a part of PHP's userbase is that? I'm guessing, it's small.
If it's small, we don't need it in the language anyway.
True, but the people who will anguish over lack of closures, are
already tearing their hair out overcreate_function()
.
Having no function and knowing it is better than having something that
looks like it but doesn't work. Saves time that would be spent
unsuccessfully trying to make it work.
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
So how big a part of PHP's userbase is that? I'm guessing, it's small.
If it's small, we don't need it in the language anyway.
I think we need it. In the current incarnation, anonymous functions
are so impractical to use, that it's a barrier. I think that is
unfortunate, because it could be an interesting and useful direction
to take for PHP. The users, who don't know what a closure is, could
still learn to use anonymous functions and they would then not assume
closures. So even if the user base is small initially (naturally,
since the feature doesn't exist), it could grow. And for the rest of
us, it's at least better than create_function()
.
Having no [closure] and knowing it is better than having something that
looks like it but doesn't work. Saves time that would be spent
unsuccessfully trying to make it work.
(I assume you meant closure?)
It can still work, without closures. Not all anonymous functions need
variable input and if they do, you can use currying. Admittedly,
currying has its own practical issues in PHP, but that's a separate
discussion. (And one, I'd like to take at a later time for that
matter)
The point is, that static lambda still has something to offer, even if
it doesn't allow closures.
--
troels
I think we need it. In the current incarnation, anonymous functions
are so impractical to use, that it's a barrier. I think that is
unfortunate, because it could be an interesting and useful direction
to take for PHP. The users, who don't know what a closure is, could
Direction like what?
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
I think we need it. In the current incarnation, anonymous functions
are so impractical to use, that it's a barrier. I think that is
unfortunate, because it could be an interesting and useful direction
to take for PHP. The users, who don't know what a closure is, couldDirection like what?
Well, the whole functional programming thing.
--
troels
Well, the whole functional programming thing.
But PHP is not an FP language and wasn't built to be one. If one needs
an FP language, why not look into languages built with that purpose?
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
Well, the whole functional programming thing.
But PHP is not an FP language and wasn't built to be one. If one needs
an FP language, why not look into languages built with that purpose?
We can change it then. PHP wasn't built to be object oriented either.
--
troels
I think we need it. In the current incarnation, anonymous functions
are so impractical to use, that it's a barrier. I think that is
unfortunate, because it could be an interesting and useful direction
to take for PHP. The users, who don't know what a closure is, couldDirection like what?
being able to do the following (and not to worry about runtime
compilation) is a good reason on it's own:
array_filter($my_data, function($test){ return 3 === ($test % 4) });
--
Alexey Zakhlestin
http://blog.milkfarmsoft.com/
being able to do the following (and not to worry about runtime
compilation) is a good reason on it's own:array_filter($my_data, function($test){ return 3 === ($test % 4) });
Oh, my..
This code clearly demonstrates why a syntax like this should not be allowed. Ever.
--
Wbr,
Antony Dovgal
being able to do the following (and not to worry about runtime
compilation) is a good reason on it's own:array_filter($my_data, function($test){ return 3 === ($test % 4) });
Oh, my..
This code clearly demonstrates why a syntax like this should not be allowed. Ever.
you prefer cluttering namespace with a lot of oneliners?
currently, people prefer to use explicit cycles instead of
array_map/array_filter and that looks ugly (hides actual logic behind
syntax), but at least it is not as slow as create_function.
--
Alexey Zakhlestin
http://blog.milkfarmsoft.com/
being able to do the following (and not to worry about runtime
compilation) is a good reason on it's own:array_filter($my_data, function($test){ return 3 === ($test % 4) });
Oh, my..
This code clearly demonstrates why a syntax like this should not be allowed. Ever.you prefer cluttering namespace with a lot of oneliners?
Oh, come on.. Since when do we call it "cluttering"?
Is there some kind of limit on number of functions in a namespace?
Why limit yourself and "inline" the function instead of putting it
into a nice library of utility functions?
currently, people prefer to use explicit cycles instead of
array_map/array_filter and that looks ugly (hides actual logic behind
syntax), but at least it is not as slow as create_function.
Whatever people currently use - it's their choice.
If you think that people would magically switch to the new syntax (if we decide to
add it after all) in a moment, I'm afraid I have to upset you - this will not happen
in the next 10 years because of many reasons, so people would still use the good
old syntax they're used to.
So here is what we actually get with this anonymous function syntax:
- Yet another way to make the code unreadable and overcomplicated.
- Yet another incompatible syntax you cannot use if you need to support older PHP versions
(and you can't check for it in runtime, since this is a compile time thingie). - 10 people happy because they got a new toy.
--
Wbr,
Antony Dovgal
you prefer cluttering namespace with a lot of oneliners?
Oh, come on.. Since when do we call it "cluttering"?
Is there some kind of limit on number of functions in a namespace?Why limit yourself and "inline" the function instead of putting it
into a nice library of utility functions?
it doesn't make sense to put some of these functions in libraries,
because they are really once-used.
another good reason is, that optimizer can safely inline this function
by eliminating function call at all (using cycle as an internal
representation, while letting keep short one in source)
currently, people prefer to use explicit cycles instead of
array_map/array_filter and that looks ugly (hides actual logic behind
syntax), but at least it is not as slow as create_function.Whatever people currently use - it's their choice.
If you think that people would magically switch to the new syntax (if we decide to
add it after all) in a moment, I'm afraid I have to upset you - this will not happen
in the next 10 years because of many reasons, so people would still use the good
old syntax they're used to.So here is what we actually get with this anonymous function syntax:
- Yet another way to make the code unreadable and overcomplicated.
it is not unreadable. it is perfectly readable for people with
modern-languages background (and I don't mean 1 or 2 languages, I mean
majority)
- Yet another incompatible syntax you cannot use if you need to support older PHP versions
(and you can't check for it in runtime, since this is a compile time thingie).
yes
- 10 people happy because they got a new toy.
"thousands" is a closer number
--
Alexey Zakhlestin
http://blog.milkfarmsoft.com/
it doesn't make sense to put some of these functions in libraries,
because they are really once-used.
It makes perfect sense to keep all your functions in one place instead of
spreading them all over the code.
And no, this kind of "optimization" doesn't make your code run any faster.
So here is what we actually get with this anonymous function syntax:
- Yet another way to make the code unreadable and overcomplicated.
it is not unreadable. it is perfectly readable for people with
modern-languages background (and I don't mean 1 or 2 languages, I mean
majority)
A new syntax that conflicts with currently existing one (or looks very similar to)
doesn't make code more readable, it adds more confusion instead.
- 10 people happy because they got a new toy.
"thousands" is a closer number
Does this mean you do admit that this is just a new toy? (pretty useless, but funny) =)
--
Wbr,
Antony Dovgal
it doesn't make sense to put some of these functions in libraries,
because they are really once-used.It makes perfect sense to keep all your functions in one place instead of
spreading them all over the code.
And no, this kind of "optimization" doesn't make your code run any faster.
if this was a comment to inline-optimization I mentioned, then it
actually would make my code run faster (I use PHP for quite unusual
tasks)
So here is what we actually get with this anonymous function syntax:
- Yet another way to make the code unreadable and overcomplicated.
it is not unreadable. it is perfectly readable for people with
modern-languages background (and I don't mean 1 or 2 languages, I mean
majority)A new syntax that conflicts with currently existing one (or looks very similar to)
doesn't make code more readable, it adds more confusion instead.
should we change "function" word on "lambda" word there to make
confusion smaller?
- 10 people happy because they got a new toy.
"thousands" is a closer numberDoes this mean you do admit that this is just a new toy? (pretty useless, but funny) =)
I admit, that I do programming, because it is fun
If it is not fun, I get bored and switch to other task
this functionality would make programming of some tasks more fun and,
as result, will lead to greater productivity
--
Alexey Zakhlestin
http://blog.milkfarmsoft.com/
(Sorry if you get this twice, Antony. I didn't hit 'Reply to all' the
first time)
being able to do the following (and not to worry about runtime
compilation) is a good reason on it's own:array_filter($my_data, function($test){ return 3 === ($test % 4) });
Oh, my..
This code clearly demonstrates why a syntax like this should not be allowed. Ever.you prefer cluttering namespace with a lot of oneliners?
Oh, come on.. Since when do we call it "cluttering"?
Is there some kind of limit on number of functions in a namespace?
Yes there is. Or more precise, there is a limited to the number of
sanely named functions.
Why limit yourself and "inline" the function instead of putting it
into a nice library of utility functions?currently, people prefer to use explicit cycles instead of
array_map/array_filter and that looks ugly (hides actual logic behind
syntax), but at least it is not as slow as create_function.Whatever people currently use - it's their choice.
If you think that people would magically switch to the new syntax (if we decide to
add it after all) in a moment, I'm afraid I have to upset you - this will not happen
in the next 10 years because of many reasons, so people would still use the good
old syntax they're used to.
I don't care what other people do with their code base. I don't want
to impose anything on them. I just want to not get a headache, when
reading my own.
So here is what we actually get with this anonymous function syntax:
- Yet another way to make the code unreadable and overcomplicated.
- Yet another incompatible syntax you cannot use if you need to support older PHP versions
(and you can't check for it in runtime, since this is a compile time thingie).
By that standard, we should never change anything in PHP. Ever.
I'm not proposing to roll this out with the next minor release. PHP6
is happening soon; It could include this patch. Of course, if we
postprone it long enough, we will have to wait for PHP7.
- 10 people happy because they got a new toy.
I don't know how to respond to that, without being rude, so I won't.
--
troels
(Sorry if you get this twice, Antony. I didn't hit 'Reply to all' the
first time)
No problem, one email more, one email less.. =)
I don't know what else I can add, so I can only repeat myself:
http://daylessday.org/archives/12-Lets-add-this,-lets-add-that.html
--
Wbr,
Antony Dovgal
No problem, one email more, one email less.. =)
I don't know what else I can add, so I can only repeat myself:
http://daylessday.org/archives/12-Lets-add-this,-lets-add-that.html
a major difference: this time there is a patch
--
Alexey Zakhlestin
http://blog.milkfarmsoft.com/
Am 20.12.2007 um 10:19 schrieb Antony Dovgal:
being able to do the following (and not to worry about runtime
compilation) is a good reason on it's own:array_filter($my_data, function($test){ return 3 === ($test %
- });
Oh, my..
This code clearly demonstrates why a syntax like this should not
be allowed. Ever.you prefer cluttering namespace with a lot of oneliners?
Oh, come on.. Since when do we call it "cluttering"?
Is there some kind of limit on number of functions in a namespace?Why limit yourself and "inline" the function instead of putting it
into a nice library of utility functions?currently, people prefer to use explicit cycles instead of
array_map/array_filter and that looks ugly (hides actual logic behind
syntax), but at least it is not as slow as create_function.Whatever people currently use - it's their choice.
If you think that people would magically switch to the new syntax
(if we decide to
add it after all) in a moment, I'm afraid I have to upset you - this
will not happen
in the next 10 years because of many reasons, so people would still
use the good
old syntax they're used to.
I think you're wrong. There are, especially recently, many examples of
libraries, frameworks and products that require a very recent version
of PHP. Not to mention "internal" projects, hosted services etc etc
etc where supporting legacy versions does not matter.
array_map($array, function($item) { return $item['foo'] == 'bar'; });
etc would be a brilliant way of making code more maintainable and
readable. most people don't create a special function for this, and
also don't use create_function, but instead write difficult to
understand loops or other constructs to process the data.
The point is that creating a function or method just for one array_map
call indeed does clutter the namespace with unnecessary stuff.
Inline functions are elegant and make code more readable and
maintanable, and that should be reason enough to have them.
Best regards,
David
Anthony,
It may be disagreeable from your perspective, but I (and many others)
find this to be a very useful construct. And I hate to invoke "other
languages" argument, but there is a reason that something like this is
available in many modern languages - it is useful.
-Andrei
Antony Dovgal wrote:
being able to do the following (and not to worry about runtime
compilation) is a good reason on it's own:array_filter($my_data, function($test){ return 3 === ($test % 4) });
Oh, my..
This code clearly demonstrates why a syntax like this should not be allowed. Ever.
I think we need it. In the current incarnation, anonymous functions
are so impractical to use, that it's a barrier. I think that is
unfortunate, because it could be an interesting and useful direction
to take for PHP. The users, who don't know what a closure is, couldDirection like what?
being able to do the following (and not to worry about runtime
compilation) is a good reason on it's own:array_filter($my_data, function($test){ return 3 === ($test % 4) });
That makes perfect sense to me as I see/use this sort of code in JavaScript.
The argument about making the filter a global function is not sensible
to apply in all circumstances. In many cases the filter is only going
to be of use in an array_filter()
call, so now we would have 2
functions to do the work of 1.
If you think about for() and foreach(), these are functional-ish
ideas. Iterators in general should quite happily support this idea.
Maybe a syntax like this would be more appealing ...
array_filter($my_data as $test) {
return 4 === ($test % 4);
}
This sort of looks like a foreach, which is extremely readable.
Richard.
--
Richard Quadling
Zend Certified Engineer : http://zend.com/zce.php?c=ZEND002498&r=213474731
"Standing on the shoulders of some very clever giants!"
That makes perfect sense to me as I see/use this sort of code in JavaScript.
Well, PHP is not Javascript - and BTW Javascript is a complex language
and I don't think we should import that complexity into PHP.
The argument about making the filter a global function is not sensible
to apply in all circumstances. In many cases the filter is only going
to be of use in anarray_filter()
call, so now we would have 2
functions to do the work of 1.
No, we won't - both cases have only one function, if you don't count
array_filter()
, and two, if you do.
Maybe a syntax like this would be more appealing ...
array_filter($my_data as $test) {
return 4 === ($test % 4);
}
No, it won't be. If you are looking for Scheme or Ruby, you know where
to find it :)
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
Right, PHP is not Javascript. But, PHP has been used alongside
Javascript more and more lately (consider AJAX and all that good stuff),
so folks who program in both would be helped by having a construct that
is both clean, readable, and similar between languages.
-Andrei
Stanislav Malyshev wrote:
That makes perfect sense to me as I see/use this sort of code in
JavaScript.Well, PHP is not Javascript - and BTW Javascript is a complex language
and I don't think we should import that complexity into PHP.
being able to do the following (and not to worry about runtime
compilation) is a good reason on it's own:array_filter($my_data, function($test){ return 3 === ($test % 4) });
What's wrong with regular functions?
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
being able to do the following (and not to worry about runtime
compilation) is a good reason on it's own:array_filter($my_data, function($test){ return 3 === ($test % 4) });
What's wrong with regular functions?
I wrote in in thread, already, but here is the summary:
- you need to declare them somewhere, which doesn't make much sense
if these functions are meant to be used only in such context.
Sometimes there are even problem to give meaningful names to such
functions - inline-function is a good candidate for automatic optimization (in
case of array_filter, array_map and similiar the construction can be
converted to the loop) - as the result of (1) and (2) it can be used for fast "currying" of
other functions
--
Alexey Zakhlestin
http://blog.milkfarmsoft.com/
- you need to declare them somewhere, which doesn't make much sense
if these functions are meant to be used only in such context.
If you need them in context, you can declare them in context.
Sometimes there are even problem to give meaningful names to such
functions
Just describe what it does. If it is not unique, then the function is
not unique.
- inline-function is a good candidate for automatic optimization (in
case of array_filter, array_map and similiar the construction can be
converted to the loop)
We have neither such optimizations nor any plans to make those, AFAIK.
- as the result of (1) and (2) it can be used for fast "currying" of
other functions
And why we need fast currying of other functions?
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
array_filter($my_data, function($test){ return 3 === ($test % 4) });
What's wrong with regular functions?
At risk of wading into yet circular another discussion on internals@,
I was part of the original thread on this subject, and I agree with
Stas here: unless they're real closure, they're pretty useless.
Apart from saving a few keystrokes, the above could easily be changed
to the following, which is much more clear, DOES compile at compile-
time, and works without adding a new construct that looks like a
closure, but is not indeed one:
function aiusdyasdjkhasdIMASHEDTHEKEYBOARD($test){ return 3 === ($test
% 4) }
array_filter($my_data, 'aiusdyasdjkhasdIMASHEDTHEKEYBOARD');
// I copy & pasted the callback name; aren't modern computers
fascinating?! (-:
S
Apart from saving a few keystrokes, the above could easily be changed
to the following, which is much more clear, DOES compile at compile-
time, and works without adding a new construct that looks like a
closure, but is not indeed one:function aiusdyasdjkhasdIMASHEDTHEKEYBOARD($test){ return 3 === ($test
% 4) }
array_filter($my_data, 'aiusdyasdjkhasdIMASHEDTHEKEYBOARD');
I'm not sure. Are you using this as an argument for or against?
Because to me, that looks ridiculous.
It won't work either. You have to add guards, to prevent a fatal
error, if you include the file twice. So the entire "solution" would
be:
if (!function_exists('aiusdyasdjkhasdIMASHEDTHEKEYBOARD')) {
function aiusdyasdjkhasdIMASHEDTHEKEYBOARD($test){ return 3 === ($test % 4); }
}
array_filter($my_data, 'aiusdyasdjkhasdIMASHEDTHEKEYBOARD');
At risk of wading into yet circular another discussion on internals@,
I was part of the original thread on this subject, and I agree with
Stas here: unless they're real closure, they're pretty useless.
I don't believe that has been Stanislavs point so far. As I understand
it, he says that adding the syntax, without adding closures, may
confuse people, who then will intuitively expect closures. That is not
the same as saying, it is useless.
On the point of uselessness, I have already explained how it would be
a useful construct.
and you did it again :-) Not working? What the hell are you talking of?
We can easily make it working with whatever feature set we want. We do notWe meaning who? I can't "easily" make closures working in PHP. And even
if I could, I'm not sure I want to.
I agree fully. Static scope should not be implemented in PHP. Could we
discus the patch, rather than something, which is purely theoretical
and only marginally related?
--
troels
Hello Stanislav,
and you did it again :-) Not working? What the hell are you talking of?
We can easily make it working with whatever feature set we want. We do not
support any feature ever thought of for everything in PHP. For instance
our object model has no abstract with default body, no MI, no delegates, no
overloading....
Back to functions. Given the long discussion about handling the non easy
stuff I think we should go with the usual PHP approach here, "Keep It Simple
Safe". As in keep everything that has to be discussed to death out. Why?
Because it allows nice stuff and has often be requested.
marcus
Wednesday, December 19, 2007, 11:21:20 PM, you wrote:
So how big a part of PHP's userbase is that? I'm guessing, it's small.
If it's small, we don't need it in the language anyway.
True, but the people who will anguish over lack of closures, are
already tearing their hair out overcreate_function()
.
Having no function and knowing it is better than having something that
looks like it but doesn't work. Saves time that would be spent
unsuccessfully trying to make it work.Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
Best regards,
Marcus
and you did it again :-) Not working? What the hell are you talking of?
We can easily make it working with whatever feature set we want. We do not
We meaning who? I can't "easily" make closures working in PHP. And even
if I could, I'm not sure I want to.
support any feature ever thought of for everything in PHP. For instance
our object model has no abstract with default body, no MI, no delegates, no
overloading....
Your point being?
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
Hi,
I was following this thread and came upon Jeff's posting on how closures
could be implemented in PHP.
Since I would find the feature to be EXTREMELY useful, I decided to
actually implement it more or less the way Jeff proposed. So, here's the
patch (against PHP_5_3, I can write one against HEAD if you whish):
http://www.christian-seiler.de/temp/closures-php-5-3.patch
I started with Wez's patch for adding anonymous functions that aren't
closures. I changed it to make sure no shift/reduce or reduce/reduce
error occur in the grammar. Then I started implementing the actual
closure stuff. It was fun because I learned quite a lot about how PHP
actually works.
I had the following main goals while developing the patch:
- Don't reinvent the wheel.
- Don't break anything unless absolutely necessary.
- Keep it simple.
Jeff proposed a new type of zval that holds additional information about
the function that is to be called. Adding a new type of zval would need
changes throughout the ENTIRE PHP source and probably also throughout
quite a few scripts. But fortunately, PHP already posesses a zval that
supports the storage of arbitrary data while being very lightweight:
Resources. So I simply added a new resource type that stores zend
functions. The $var = function () {}; will now make $var a resource (of
the type "anonymous function".
Anonymous functions are ALWAYS defined at compile time, no matter where
they occur. They are simply named __compiled_lamda_1234 and added to the
global function table. But instead of simply returning the string
'__compiled_lambda_1234', I introduced a new opcode that will create
references to the correct local variables that are referenced inside the
function.
For example, if you have:
$func = function () {
echo "Hello World\n";
};
This will result in an anonymous function called '__compiled_lambda_0'
that is added to the function table at compile time. The opcode for the
assignment to $func will be something like:
1 ZEND_DECLARE_ANON_FUNC ~0 '__compiled_lambda_0'
2 ASSIGN !0, ~0
The ZEND_DECLARE_ANON_FUNC opcode handler does the following:
It creates a new zend_function, copies the contents of the entire
structure of the function table entry corresponding to
'__compiled_lamda_0' into that new structure, increments the refcount,
registeres it as a resource and returns that resource so it can be
assigned to the variable.
Now, have a look at a real closure:
$string = "Hello World!\n";
$func = function () {
lexical $string;
echo $string;
};
This will result in the same opcode as above. But here, three additional
things happen:
-
The compiler sees the keyword 'lexical' and stores the information,
that a variable called 'string' should be used inside the closure. -
The opcode handler sees that a variable named 'string' is marked as
lexical in the function definition. Therefore it creates a reference to
it in a HashTable of the COPIED zend_function (that will be stored in
the resource). -
The 'lexical $string;' translates into a FETCH opcode that will work
in exactly the same way as 'static' or 'global' - only fetching it from
the additional HashTable in the zend_function structure.
The resource destructor makes sure that the HashTable containing the
references to the lexical veriables is correctly destroyed upon
destruction of the resource. It does NOT destroy other parts of the
function structure because they will be freed when the function is
removed from the global function table.
With these changes, closures work in PHP.
Some caveats / bugs / todo:
-
Calling anonymous functions by name directly is problematic if there
are lexical variables that need to be assigned. I added checks to
make sure this case does not happen. -
In the opcode handler, error handling needs to be added.
-
If somebody removes the function from the global function table,
(e.g. with runkit), the new opcode will returnNULL
instead of
a resource (error handling is missing). Since I do increment
refcount of the zend_function, it SHOULD not cause segfaults or
memory leaks, but I haven't tested it. -
$this is kind of a problem, because all the fetch handlers in PHP
make sure $this is a special kind of variable. For the first version
of the patch I chose not to care about this because what still works
is e.g. the following:$object = $this; $func = function () { lexical $object; // do something };
Also, inside the closures, the class context is not preserved, so
accessing private / protected members is not possible.I'm not sure this actually represents a problem because you can
always use normal local variables to pass values between closure
and calling method and make the calling method change the properties
itself. -
I've had some problems with eval(), have a look at the following
code:$func = eval ('return function () { echo "Hello World!\n"; };'); $func();
With plain PHP, this seems to work, with the VLD extension loaded
(that shows the Opcodes), it crashes. I don't know if that's a
problem with eval() or just with VLD and I didn't have time to
investigate it further. -
Oh, yes, 'lexical' is now a keyword. Although I really don't think
that TOO many people use that as an identifier, so it probably won't
hurt THAT much.
Except those above points, it really works, even with complex stuff. Let
me show you some examples:
- Customized array_filter:
function filter_larger ($array, $min = 42) {
$filter = function ($value) {
lexical $min;
return ($value >= $min);
};
return array_filter ($array, $filter);
}
$arr = array (41, 43);
var_dump (filter_larger ($arr)); // 43
var_dump (filter_larger ($arr, 40)); // 41, 43
var_dump (filter_larger ($arr, 44)); // empty
- Jeff's example:
function getAdder($x) {
return function ($y) {
lexical $x;
return $x + $y;
};
}
$plusFive = getAdder(5);
$plusTen = getAdder(10);
echo $plusFive(4)."\n"; // 9
echo $plusTen(7)."\n"; // 17
- Nested closures
$outer = function ($value) {
return function () {
lexical $value;
return $value * 2;
};
};
$duplicator = $outer (4);
echo $duplicator ()."\n"; // 8
$duplicator = $outer (8);
echo $duplicator ()."\n"; // 16
[Ok, yeah, that example is quite stupid and should NOT be used as an
example for good code. ;-) But it's simple and demonstrates the
possibilities.]
It would be great if somebody could review the patch because I'm shure
some parts can still be cleaned up or improved. And it would be even
better if this feature would make it into PHP. ;-)
Regards,
Christian
PS: I'd like to thank Derick Rethans for his GREAT Vulcan Logic
Disassembler - without it, developement would have been a LOT more painful.
PPS: Oh, yeah, if it should be legally necessary, I grant the right to
anybody to use this patch under any OSI certified license you may want
to choose.
Christian Seiler wrote:
Hi,
I was following this thread and came upon Jeff's posting on how closures
could be implemented in PHP.
Hi,
typo alert:
Index: Zend/zend_vm_def.h
RCS file: /repository/ZendEngine2/zend_vm_def.h,v
retrieving revision 1.59.2.29.2.48.2.25
diff -u -r1.59.2.29.2.48.2.25 zend_vm_def.h
--- Zend/zend_vm_def.h 14 Dec 2007 14:14:50 -0000 1.59.2.29.2.48.2.25
+++ Zend/zend_vm_def.h 22 Dec 2007 13:52:44 -0000
@@ -1,4 +1,4 @@
-/*
+f/*
Very impressive patch, I'll be interested to try it out when I get a chance.
Greg
Hi!
typo alert:
Oh, thanks (don't know how it got in there ;-)), I fixed that, same
address:
http://www.christian-seiler.de/temp/closures-php-5-3.patch
Very impressive patch, I'll be interested to try it out when I get a chance.
Thanks!
Christian
One question about the names you generate for the function table in
combination with opcode caches.
Let's assume I have APC installed, and do the following:
foo.php:
$foo = function() {
echo "foo";
}
bar.php:
include('foo.php');
All works fine, and cached versions of both files would be created.
However, if I now change bar.php to this:
$bar = function() {
echo "bar";
}
include('foo.php');
wouldn't I get a "cannot redefine function __compiled_lamda_0" fatal,
if foo.php was loaded from the opcode cache?
While this is a constructed example, it could easily occur with
conditional includes with environments that use opcode caches.
Or am I completely wrong? :p
- David
Am 22.12.2007 um 16:08 schrieb Christian Seiler:
Hi,
I was following this thread and came upon Jeff's posting on how
closures
could be implemented in PHP.Since I would find the feature to be EXTREMELY useful, I decided to
actually implement it more or less the way Jeff proposed. So, here's
the
patch (against PHP_5_3, I can write one against HEAD if you whish):http://www.christian-seiler.de/temp/closures-php-5-3.patch
I started with Wez's patch for adding anonymous functions that aren't
closures. I changed it to make sure no shift/reduce or reduce/reduce
error occur in the grammar. Then I started implementing the actual
closure stuff. It was fun because I learned quite a lot about how PHP
actually works.I had the following main goals while developing the patch:
- Don't reinvent the wheel.
- Don't break anything unless absolutely necessary.
- Keep it simple.
Jeff proposed a new type of zval that holds additional information
about
the function that is to be called. Adding a new type of zval would
need
changes throughout the ENTIRE PHP source and probably also throughout
quite a few scripts. But fortunately, PHP already posesses a zval that
supports the storage of arbitrary data while being very lightweight:
Resources. So I simply added a new resource type that stores zend
functions. The $var = function () {}; will now make $var a resource
(of
the type "anonymous function".Anonymous functions are ALWAYS defined at compile time, no matter
where
they occur. They are simply named __compiled_lamda_1234 and added to
the
global function table. But instead of simply returning the string
'__compiled_lambda_1234', I introduced a new opcode that will create
references to the correct local variables that are referenced inside
the
function.For example, if you have:
$func = function () {
echo "Hello World\n";
};This will result in an anonymous function called '__compiled_lambda_0'
that is added to the function table at compile time. The opcode for
the
assignment to $func will be something like:1 ZEND_DECLARE_ANON_FUNC ~0 '__compiled_lambda_0' 2 ASSIGN !0, ~0
The ZEND_DECLARE_ANON_FUNC opcode handler does the following:
It creates a new zend_function, copies the contents of the entire
structure of the function table entry corresponding to
'__compiled_lamda_0' into that new structure, increments the refcount,
registeres it as a resource and returns that resource so it can be
assigned to the variable.Now, have a look at a real closure:
$string = "Hello World!\n";
$func = function () {
lexical $string;
echo $string;
};This will result in the same opcode as above. But here, three
additional
things happen:
The compiler sees the keyword 'lexical' and stores the information,
that a variable called 'string' should be used inside the closure.The opcode handler sees that a variable named 'string' is marked as
lexical in the function definition. Therefore it creates a reference
to
it in a HashTable of the COPIED zend_function (that will be stored in
the resource).The 'lexical $string;' translates into a FETCH opcode that will
work
in exactly the same way as 'static' or 'global' - only fetching it
from
the additional HashTable in the zend_function structure.The resource destructor makes sure that the HashTable containing the
references to the lexical veriables is correctly destroyed upon
destruction of the resource. It does NOT destroy other parts of the
function structure because they will be freed when the function is
removed from the global function table.With these changes, closures work in PHP.
Some caveats / bugs / todo:
Calling anonymous functions by name directly is problematic if there
are lexical variables that need to be assigned. I added checks to
make sure this case does not happen.In the opcode handler, error handling needs to be added.
If somebody removes the function from the global function table,
(e.g. with runkit), the new opcode will returnNULL
instead of
a resource (error handling is missing). Since I do increment
refcount of the zend_function, it SHOULD not cause segfaults or
memory leaks, but I haven't tested it.$this is kind of a problem, because all the fetch handlers in PHP
make sure $this is a special kind of variable. For the first version
of the patch I chose not to care about this because what still works
is e.g. the following:$object = $this; $func = function () { lexical $object; // do something };
Also, inside the closures, the class context is not preserved, so
accessing private / protected members is not possible.I'm not sure this actually represents a problem because you can
always use normal local variables to pass values between closure
and calling method and make the calling method change the
properties
itself.I've had some problems with eval(), have a look at the following
code:$func = eval ('return function () { echo "Hello World!\n"; };'); $func();
With plain PHP, this seems to work, with the VLD extension loaded
(that shows the Opcodes), it crashes. I don't know if that's a
problem with eval() or just with VLD and I didn't have time to
investigate it further.Oh, yes, 'lexical' is now a keyword. Although I really don't think
that TOO many people use that as an identifier, so it probably won't
hurt THAT much.Except those above points, it really works, even with complex stuff.
Let
me show you some examples:
- Customized array_filter:
function filter_larger ($array, $min = 42) {
$filter = function ($value) {
lexical $min;
return ($value >= $min);
};
return array_filter ($array, $filter);
}$arr = array (41, 43);
var_dump (filter_larger ($arr)); // 43
var_dump (filter_larger ($arr, 40)); // 41, 43
var_dump (filter_larger ($arr, 44)); // empty
- Jeff's example:
function getAdder($x) {
return function ($y) {
lexical $x;
return $x + $y;
};
}$plusFive = getAdder(5);
$plusTen = getAdder(10);echo $plusFive(4)."\n"; // 9
echo $plusTen(7)."\n"; // 17
- Nested closures
$outer = function ($value) {
return function () {
lexical $value;
return $value * 2;
};
};$duplicator = $outer (4);
echo $duplicator ()."\n"; // 8
$duplicator = $outer (8);
echo $duplicator ()."\n"; // 16[Ok, yeah, that example is quite stupid and should NOT be used as an
example for good code. ;-) But it's simple and demonstrates the
possibilities.]It would be great if somebody could review the patch because I'm shure
some parts can still be cleaned up or improved. And it would be even
better if this feature would make it into PHP. ;-)Regards,
ChristianPS: I'd like to thank Derick Rethans for his GREAT Vulcan Logic
Disassembler - without it, developement would have been a LOT more
painful.PPS: Oh, yeah, if it should be legally necessary, I grant the right to
anybody to use this patch under any OSI certified license you may want
to choose.
I have another observation about names.
Instead of using an arbitrary name, as the name of the function,
wouldn't it be possible to let the name be derived from the
function-body. Eg., if you took the function-body's tokens and created
a hash from them. This would have two implications: 1) Multiple
definitions of the same function would be optimised into one. And more
importantly 2) , it would be possible to serialize/unserialize a
closure. Of course, this won't work if an anonymous function is a
resource, since resources can't be serialized. This would work for
Wez' original patch though.
--
troels
Hi!
I'm going to answer to everybody at once, if that's OK.
David Zülke wrote:
One question about the names you generate for the function table in
combination with opcode caches.
[...]
While this is a constructed example, it could easily occur with
conditional includes with environments that use opcode caches.
Oh, yes, that's true, I didn't think of that, thanks a lot for pointing
that out!
Tomorrow I'll post an updated patch which will make sure this doesn't
happen (see below).
troels knak-nielsen wrote:
I have another observation about names.
Instead of using an arbitrary name, as the name of the function,
wouldn't it be possible to let the name be derived from the
function-body. Eg., if you took the function-body's tokens and created
a hash from them. This would have two implications: 1) Multiple
definitions of the same function would be optimised into one. And more
importantly 2) , it would be possible to serialize/unserialize a
closure. Of course, this won't work if an anonymous function is a
resource, since resources can't be serialized. This would work for
Wez' original patch though.
Thanks for the suggestions, and although I don't completely agree with
you, you pointed me into the direction I'm leaning towards now, so
thanks. :-)
First of all: I don't quite understand what you mean when you want to
serialize a function (closure or not)? The opcodes? Ok, sure, with the
current PHP implementation you can serialize the variable used to CALL
the function (e.g. with $func = 'str_replace'; $func is only a string
and can be serialized). But where would you need that? (Ok, for normal
functions that are named this could actually be useful, but for
anonymous functions?)
Using the function body itself as a hashed value for the function name
would require some major changes to the parser. Currently, the
function name has to be known before the function body starts. Also, the
tokens inside the function would have to be tracked and stored in an
array somehow. This would be quite a performance penalty during compile
time.
But: The idea I hat thanks to you is to use the file name and a per-file
counter for the function name. So the name would be something like
(symbollically speaking) 'compiled_lambda' + hashfunction(FILE) +
'' + per_file_counter. That would solve the problem with opcode caches
while not causing any real performance penalties (the hash of the loaded
file would only have to be calculated once).
As I also wrote above, I'll post an updated patch tomorrow that will
address this problem.
Martin Alterisio wrote:
That's very kind of you but, if I was explained right, you don't have
copyright on a patch. [...]
I don't agree at all (except for the part where a patch is a derived
work) but since I don't want to talk about copyright laws here (no
offense, but I'd rather spend my time writing code), I think we both can
live with the following statement: If you're right, the PHP team can use
the patch anyway, so it isn't a problem. If I'm right, I've given the
PHP team the necessary permission to use it, so it isn't a problem either.
Regards,
Christian
First of all: I don't quite understand what you mean when you want to
serialize a function (closure or not)? The opcodes? Ok, sure, with the
current PHP implementation you can serialize the variable used to CALL
the function (e.g. with $func = 'str_replace'; $func is only a string
Admittedly, I got the idea, in relation to Wez' patch. There, it would
be trivial to serialize/unserialize the function pointer. I suppose
that isn't immediately possible, if the closure is a resource type.
and can be serialized). But where would you need that? (Ok, for normal
functions that are named this could actually be useful, but for
anonymous functions?)
If a closure could be serialized, it would allow for continuation
style applications. I'm not sure, how good idea this is in web
applications anyway, so just ignore that.
Just a minor note; The semi-colon after the closing brace, seems
superfluous. Is there any reason for it?
Otherwise excellent work there.
--
troels
Hi!
Just a minor note; The semi-colon after the closing brace, seems
superfluous. Is there any reason for it?
Unfortunately, yes. The problem is that the closure must be an
expression so it can a) be assigned to a variable and b) returned
directly. And since the expression is only a part of a statement, I can
take no influence at that point in the grammar as to whether a semicolon
should follow or not.
I don't see any way of removing the semicolon without a) either
making the language inconsistent and/or b) adding a lot of bloat to the
grammar.
Regards,
Christian
Hi David!
One question about the names you generate for the function table in
combination with opcode caches. [...]
I now updated the patch so that this problem is addressed. You will find
it here:
http://www.christian-seiler.de/temp/closures-php-5-3-v2.patch
The compiled functions are now named compiled_lambda$hash$counter,
where $counter is a per-file lambda counter and $hash is a hash made
from the file name (see hash_compiled_filename in zend_compile.c, I
wasn't sure how good Zend's hash function is with duplicates so I hashed
the file name AND its basename - feel free to change that function if
you have a better idea or know it's safe to only hash the filename
itself because duplicates are too rare).
Regards,
Christian
Cool. I hope I'll have the time to give that a shot over the holidays.
Great work, nice patch (from what I, with my rubbish knowledge, can
tell :D)
David
Am 23.12.2007 um 14:40 schrieb Christian Seiler:
Hi David!
One question about the names you generate for the function table in
combination with opcode caches. [...]I now updated the patch so that this problem is addressed. You will
find
it here:http://www.christian-seiler.de/temp/closures-php-5-3-v2.patch
The compiled functions are now named compiled_lambda$hash$counter,
where $counter is a per-file lambda counter and $hash is a hash made
from the file name (see hash_compiled_filename in zend_compile.c, I
wasn't sure how good Zend's hash function is with duplicates so I
hashed
the file name AND its basename - feel free to change that function if
you have a better idea or know it's safe to only hash the filename
itself because duplicates are too rare).Regards,
Christian
I now updated the patch so that this problem is addressed. You will find
it here:http://www.christian-seiler.de/temp/closures-php-5-3-v2.patch
A few minor items from a quick read-through of the patch:
-
There appear to be some spurious whitespace insertions in this
version of the patch. -
The terms "lamba" and "anonymous function" are being used
interchangeably. If we're going to introduce new terminology, it
would be good to pick one name and use it consistently. I don't
have a preference for which one is ultimately chosen.The term "lexical" could also be considered a competing term as
its used in part of the patch. -
The "is_anonymous" flags could be zend_bool values instead of bare
integers, although that breaks the precedent started by some
related flags (such as "is_method"). -
This part of the zend_vm_def.h diff looks wrong (a stray "f"):
-/* +f/*
Looks great overall!
--
Jon Parise (jon of php.net) :: The PHP Project (http://www.php.net/)
Hi!
Thanks for reading through!
- There appear to be some spurious whitespace insertions in this
version of the patch.
Oh, that's probably my editor, I'll fix that.
- The terms "lamba" and "anonymous function" are being used
interchangeably. If we're going to introduce new terminology, it
would be good to pick one name and use it consistently. I don't
have a preference for which one is ultimately chosen.
Well, create_function uses an already-existing EG(lambda_count) and
names the function _lambda$counter so I thought I'd use
CG(compiled_lambda_count) and name them _compiled_lambda... But since
anonymous functions aren't REAL lambdas, I named them anonymous
elsewhere. But you're right, introducing duplicate terminology is a bad
idea, I'll change everything to lambda for consistency, even though it's
technically not 100% correct.
The term "lexical" could also be considered a competing term as its used in part of the patch.
'lexical' is only used for the variables that are passed into the
closure, not for the closure itself.
- The "is_anonymous" flags could be zend_bool values instead of bare
integers, although that breaks the precedent started by some
related flags (such as "is_method").
You're right, zend_bool is a better idea. Since PHP 5.3 is going to
break binary compability anyway, would it do any harm changing the
types of the existing flags, too?
This part of the zend_vm_def.h diff looks wrong (a stray "f"):
-/* +f/*
WTF? I thought I had already fixed that. Hmm, obviously I hadn't...
Looks great overall!
Thanks!
Merry Christmas,
Christian
- The "is_anonymous" flags could be zend_bool values instead of bare
integers, although that breaks the precedent started by some
related flags (such as "is_method").You're right, zend_bool is a better idea. Since PHP 5.3 is going to
break binary compability anyway, would it do any harm changing the
types of the existing flags, too?
I don't have a strong opinion one way or another, but other folks
might.
I think using it would make the API clearer, and there might be memory
size benefits when used as a structure member.
On the other hand, changing those integers to zend_bool (unsigned
char) types could potentially have a runtime performance cost on some
platforms (depending on how those values are being marshaled around),
but that would have to be measured.
--
Jon Parise (jon of php.net) :: The PHP Project (http://www.php.net/)
any idea about the possibility of hash conflict?
2007/12/22, Christian Seiler chris_se@gmx.net:
PPS: Oh, yeah, if it should be legally necessary, I grant the right to
anybody to use this patch under any OSI certified license you may want
to choose.
That's very kind of you but, if I was explained right, you don't have
copyright on a patch. If I understood the legalese correctly, you retain
authorship on the code provided, but this kind of derived work, which has no
purpose outside the original, cannot be claimed through copyright. You
specially did it for the php codebase, and has not a meaning outside it.
Consider what happens to the code you make to maintain an in-house
application of a company. You cannot claim copyright on that codebase, it
remains to the company. But, if you were to develop an app from scratch for
the same company, the company has the right to use it in-house, but if they
want to distribute it, they have to require your consent, because you're one
of the copyright holders.
Also consider what happens to reviewers that check and correct works from
other people, such as syntax and grammar checking in literature, or code
reviewing in software. They can claim authorship on their corrections, but
they cannot claim copyright.
Anyway, it's too complicate. The bottom line is that copyright cannot be
claimed on patches. Which makes complete sense, imagine what would happen to
open-source if it wasn't this way... the licensing documentation would
occupy more than the code itself...
2007/12/22, Christian Seiler chris_se@gmx.net:
Hi,
I was following this thread and came upon Jeff's posting on how closures
could be implemented in PHP.Since I would find the feature to be EXTREMELY useful, I decided to
actually implement it more or less the way Jeff proposed. So, here's the
patch (against PHP_5_3, I can write one against HEAD if you whish):
Hi,
I finally got some time to check out your patch (the first and the second
version). It looks cool, works as expected in most of the things I've tried.
Nevertheless, I don't need this feature, it doesn't provide any advantage
over the functor objects I've been using since long. Closures and anonymous
functions are harder to maintain and debug, and they are not as elegant as
their counterparts in functional languages (lambda functions). On the other
side, functor objects are more maintainable, scalable, elegant, and
versatile (and php already supports them... it could be improved though).
Bottom line, excellent work. If a considerable part of the php user base
need this, excellent, they have an implementation which seems viable. But,
sorry, I couldn't care less about closures or anonymous functions. Anyway,
my opinion doesn't matter, I'm just one user.
Best Regards,
Martin Alterisio
Hello Christian,
I have put your proposal as a link to a PHP GSoC 2008 idea here:
http://wiki.php.net/gsoc/2008
Feel invited to add to this idea in whatever way you want :-)
marcus
Saturday, December 22, 2007, 4:08:04 PM, you wrote:
Hi,
I was following this thread and came upon Jeff's posting on how closures
could be implemented in PHP.
Since I would find the feature to be EXTREMELY useful, I decided to
actually implement it more or less the way Jeff proposed. So, here's the
patch (against PHP_5_3, I can write one against HEAD if you whish):
I started with Wez's patch for adding anonymous functions that aren't
closures. I changed it to make sure no shift/reduce or reduce/reduce
error occur in the grammar. Then I started implementing the actual
closure stuff. It was fun because I learned quite a lot about how PHP
actually works.
I had the following main goals while developing the patch:
- Don't reinvent the wheel.
- Don't break anything unless absolutely necessary.
- Keep it simple.
Jeff proposed a new type of zval that holds additional information about
the function that is to be called. Adding a new type of zval would need
changes throughout the ENTIRE PHP source and probably also throughout
quite a few scripts. But fortunately, PHP already posesses a zval that
supports the storage of arbitrary data while being very lightweight:
Resources. So I simply added a new resource type that stores zend
functions. The $var = function () {}; will now make $var a resource (of
the type "anonymous function".
Anonymous functions are ALWAYS defined at compile time, no matter where
they occur. They are simply named __compiled_lamda_1234 and added to the
global function table. But instead of simply returning the string
'__compiled_lambda_1234', I introduced a new opcode that will create
references to the correct local variables that are referenced inside the
function.
For example, if you have:
$func = function () {
echo "Hello World\n";
};
This will result in an anonymous function called '__compiled_lambda_0'
that is added to the function table at compile time. The opcode for the
assignment to $func will be something like:
1 ZEND_DECLARE_ANON_FUNC ~0 '__compiled_lambda_0' 2 ASSIGN !0, ~0
The ZEND_DECLARE_ANON_FUNC opcode handler does the following:
It creates a new zend_function, copies the contents of the entire
structure of the function table entry corresponding to
'__compiled_lamda_0' into that new structure, increments the refcount,
registeres it as a resource and returns that resource so it can be
assigned to the variable.
Now, have a look at a real closure:
$string = "Hello World!\n";
$func = function () {
lexical $string;
echo $string;
};
This will result in the same opcode as above. But here, three additional
things happen:
- The compiler sees the keyword 'lexical' and stores the information,
that a variable called 'string' should be used inside the closure.
- The opcode handler sees that a variable named 'string' is marked as
lexical in the function definition. Therefore it creates a reference to
it in a HashTable of the COPIED zend_function (that will be stored in
the resource).
- The 'lexical $string;' translates into a FETCH opcode that will work
in exactly the same way as 'static' or 'global' - only fetching it from
the additional HashTable in the zend_function structure.
The resource destructor makes sure that the HashTable containing the
references to the lexical veriables is correctly destroyed upon
destruction of the resource. It does NOT destroy other parts of the
function structure because they will be freed when the function is
removed from the global function table.
With these changes, closures work in PHP.
Some caveats / bugs / todo:
- Calling anonymous functions by name directly is problematic if there
are lexical variables that need to be assigned. I added checks to
make sure this case does not happen.
- In the opcode handler, error handling needs to be added.
- If somebody removes the function from the global function table,
(e.g. with runkit), the new opcode will returnNULL
instead of
a resource (error handling is missing). Since I do increment
refcount of the zend_function, it SHOULD not cause segfaults or
memory leaks, but I haven't tested it.
- $this is kind of a problem, because all the fetch handlers in PHP
make sure $this is a special kind of variable. For the first version
of the patch I chose not to care about this because what still works
is e.g. the following:
$object = $this; $func = function () { lexical $object; // do something };
Also, inside the closures, the class context is not preserved, so accessing private / protected members is not possible.
I'm not sure this actually represents a problem because you can always use normal local variables to pass values between closure and calling method and make the calling method change the properties itself.
- I've had some problems with eval(), have a look at the following
code:
$func = eval ('return function () { echo "Hello World!\n"; };'); $func();
With plain PHP, this seems to work, with the VLD extension loaded
(that shows the Opcodes), it crashes. I don't know if that's a
problem with eval() or just with VLD and I didn't have time to
investigate it further.
- Oh, yes, 'lexical' is now a keyword. Although I really don't think
that TOO many people use that as an identifier, so it probably won't
hurt THAT much.
Except those above points, it really works, even with complex stuff. Let
me show you some examples:
- Customized array_filter:
function filter_larger ($array, $min = 42) {
$filter = function ($value) {
lexical $min;
return ($value >= $min);
};
return array_filter ($array, $filter);
}
$arr = array (41, 43);
var_dump (filter_larger ($arr)); // 43
var_dump (filter_larger ($arr, 40)); // 41, 43
var_dump (filter_larger ($arr, 44)); // empty
- Jeff's example:
function getAdder($x) {
return function ($y) {
lexical $x;
return $x + $y;
};
}
$plusFive = getAdder(5);
$plusTen = getAdder(10);
echo $plusFive(4)."\n"; // 9
echo $plusTen(7)."\n"; // 17
- Nested closures
$outer = function ($value) {
return function () {
lexical $value;
return $value * 2;
};
};
$duplicator = $outer (4);
echo $duplicator ()."\n"; // 8
$duplicator = $outer (8);
echo $duplicator ()."\n"; // 16
[Ok, yeah, that example is quite stupid and should NOT be used as an
example for good code. ;-) But it's simple and demonstrates the
possibilities.]
It would be great if somebody could review the patch because I'm shure
some parts can still be cleaned up or improved. And it would be even
better if this feature would make it into PHP. ;-)
Regards,
Christian
PS: I'd like to thank Derick Rethans for his GREAT Vulcan Logic
Disassembler - without it, developement would have been a LOT more painful.
PPS: Oh, yeah, if it should be legally necessary, I grant the right to
anybody to use this patch under any OSI certified license you may want
to choose.
Best regards,
Marcus
I think the problem there is that this syntax wouldn't support external
variables, and without them there's not much difference between that and
create_function.
The difference is, that it is compile-time and create_function is run-time
troels knak-nielsen wrote:
What was the conclusion on Wez' patch from march [1]? The discussion
seemed to steer a bit off, on the discussion of scoping rules, but is
there any reason not to implement Wez' patch in HEAD?
Even if it doesn't entirely replace create_function, it would be nice
to have as a compile-time alternative.--
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com--
--
Alexey Zakhlestin
http://blog.milkfarmsoft.com/
Hello Alexey,
and for that reason we should do it! Can't Wez simply apply this?
marcus
Sunday, December 16, 2007, 3:22:40 PM, you wrote:
I think the problem there is that this syntax wouldn't support external
variables, and without them there's not much difference between that and
create_function.
The difference is, that it is compile-time and create_function is run-time
troels knak-nielsen wrote:
What was the conclusion on Wez' patch from march [1]? The discussion
seemed to steer a bit off, on the discussion of scoping rules, but is
there any reason not to implement Wez' patch in HEAD?
Even if it doesn't entirely replace create_function, it would be nice
to have as a compile-time alternative.--
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com--
--
Alexey Zakhlestin
http://blog.milkfarmsoft.com/
Best regards,
Marcus
I actually remember there was some good discussion on this and there
were pretty decent reasons for why this patch was not a full solution
and only syntactic sugar, i.e. it didn't do what many expected except
for making the syntax nicer.
Don't have time right now but we should go back and re-read those
discussions before opening a new one. I can probably do it over the next
couple of days. I am not necessary against supporting such a solution
but we need to make sure it's fully baked and that we clear know and
agree on what is and what isn't supported (and that should be documented
and not revisited every time someone comes and complains). Variable
binding is one thing which would likely not make it into such an
implementation unless there's a big shift in the intent of the patch.
We really need to get away from this "let's just commit it" mode on this
list. As you saw with garbage collection, namespaces and other recent
topics a lot of these topics need significantly more work before they
are full baked and ready to actually make it into the code base. The
ad-hoc way of committing new features just doesn't work.
Andi
-----Original Message-----
From: Marcus Boerger [mailto:helly@php.net]
Sent: Monday, December 17, 2007 5:10 AM
To: Alexey Zakhlestin; Wez Furlong
Cc: Stas Malyshev; troels knak-nielsen; internals@lists.php.net
Subject: Re: [PHP-DEV] Re: PATCH: anonymous functions in PHPHello Alexey,
and for that reason we should do it! Can't Wez simply apply this?
marcus
Sunday, December 16, 2007, 3:22:40 PM, you wrote:
I think the problem there is that this syntax wouldn't support
external
variables, and without them there's not much difference between
that
and
create_function.The difference is, that it is compile-time and create_function is
run-timetroels knak-nielsen wrote:
What was the conclusion on Wez' patch from march [1]? The
discussion
seemed to steer a bit off, on the discussion of scoping rules,
but
is
there any reason not to implement Wez' patch in HEAD?
Even if it doesn't entirely replace create_function, it would be
nice
to have as a compile-time alternative.[1] http://groups.google.com/group/mailing.www.php-
dev/browse_thread/thread/a2c3296dc791369a/075209b288cb28de--
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com--
--
Alexey Zakhlestin
http://blog.milkfarmsoft.com/Best regards,
Marcus
Andi Gutmans wrote:
We really need to get away from this "let's just commit it" mode on this
list. As you saw with garbage collection, namespaces and other recent
topics a lot of these topics need significantly more work before they
are full baked and ready to actually make it into the code base. The
ad-hoc way of committing new features just doesn't work.
Amen to that. A long time ago we wanted to use text files in an RFC
directory in CVS to discuss new language features. Maybe we should
resurrect this idea?
--
Sebastian Bergmann http://sebastian-bergmann.de/
GnuPG Key: 0xB85B5D69 / 27A7 2B14 09E4 98CD 6277 0E5B 6867 C514 B85B 5D69
Don't have time right now but we should go back and re-read those
discussions before opening a new one. I can probably do it over the next
couple of days. I am not necessary against supporting such a solution
but we need to make sure it's fully baked and that we clear know and
agree on what is and what isn't supported (and that should be documented
and not revisited every time someone comes and complains). Variable
binding is one thing which would likely not make it into such an
implementation unless there's a big shift in the intent of the patch.
Allow me to recap then.
Static lambdas (in lack of a better name) and create_function()
differ
from each other in that the former happens at compile time, while the
latter happens at run time. The consequence of this is, that you can
bind (primitive) variables to the created function, with
create_function()
, but not with static lambdas.
Functionally, create_function()
is a variation of eval, which allows
you to create new code at runtime. Static lambda is syntactic sugar
for creating a function in the global scope, without knowing its name
at compile time. Static lambda is more restrictive than
create_function()
, because there is no way to share scope between the
inner and the bounding function. This is specific for PHP, because the
language doesn't have static scope. Without going much into details, I
think it's pretty safe to say, that introducing static scope into PHP
is not only undesireable, but also technically impossible. So let's
not spend any time discussing that.
In brief, there are the following benefits of introducing static lambdas:
A1 Better readability.
A2 Syntactic errors happen at compile time, rather than run time.
A3 Better support for debugging/introspection.
A4 Optimisations are possible.
And the following problems:
B1 Users might assume, that lambdas have static scope.
B2 There is some overlap between the application of create_function()
and static lambdas, meaning some level of redundancy.
B3 Static lambdas are less powerful, since there is no way to bind variables.
If I missed any points, please bring them up. Otherwise, this is what
a decision should be based on. (And sorry for pointing out the
obvious, but not making a decision, is a decision too)
Of the cons, B1 is probably the biggest issue. There are (at least)
two considerations to take here; First, how probable is it, that users
will make this assumption? And second, how much confusion will it
cause to those, which it affects?
The first question could perhaps be answered by a survey (Or less
scientifically, by random consensus), but the latter is rather
subjective. I'd like to make the argument, that those who will tend to
suffer most from peculiarities of scoping rules, are the same people,
who are less likely to have encountered static scope in the first
place. The simple rule, that PHP has dynamic scope, should be simple
to understand for anyone who actually knows the difference.
Did I miss anything?
--
troels
Functionally,
create_function()
is a variation of eval, which allows
you to create new code at runtime. Static lambda is syntactic sugar
for creating a function in the global scope, without knowing its name
at compile time. Static lambda is more restrictive than
While we are at it, what's wrong with knowing the name? I can see why
closure can be fun when you can dynamically use outer-scope variables.
But when you can't, what exactly prevents one from just doing the function?
two considerations to take here; First, how probable is it, that users
will make this assumption? And second, how much confusion will it
cause to those, which it affects?
First, about 100% on the first encounter for any user ever seeing
closures in any other language. Second, all the confusion possible, like
"oh, closures! cool! let me do this and that! what, I can't use
variables?! Are you kidding me?! WTF is it useful for?!"
place. The simple rule, that PHP has dynamic scope, should be simple
to understand for anyone who actually knows the difference.
I'm not sure I understand what you mean by "static scope" and "dynamic
scope", but anyway thing that looks like closure but works like regular
function definition that isn't - is not a very good idea, IMO.
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
and for that reason we should do it! Can't Wez simply apply this?
I think we saw numerous examples that "commit first, think later"
approach is not the best one...
--
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
Hello Stanislav,
I do not see anything broken here. All I see is discussing a patch to
death just because it doesn't do what everybody wants.
marcus
Monday, December 17, 2007, 6:14:22 PM, you wrote:
and for that reason we should do it! Can't Wez simply apply this?
I think we saw numerous examples that "commit first, think later"
approach is not the best one...
--
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
Best regards,
Marcus