PHP Closures Update
Over the weekend I was experimenting with my PHP Closures implementation. Unfortunately I ran into some bugs, but those I was able to fix. The first and main bug that I ran into was particularly interesting and required an ugly fix.
First, lets set up an example and display it visually...
Imagine that each box in the above image represents a different function/lambda. What we can see is that there is a lambda within a lambda within a function. Simple enough. The arrows are where the problems come in. The first iteration of the Lambda class was able to reproduce the first arrow, meaning that scope variables from the parent functions were successfully brought down into child functions. This was accomplished by using PHP's get_defined_vars() coupled with extract().
Bringing the modified scope variables out of the sub-lambdas and up into parent functions (the bottom array) was somewhat trickier. First, some obvious observations needed to be made: the scope variables will only be modified when the lambda's call() method is called. Also, call() can return anything and can be used anywhere, so we can't make any assumptions about its use. Therefore, whatever the solution is, somehow it will have to take place after the call() method is called. The solution I came up solves this problem for anything within a lambda; however, I didn't want to subject the programmer to having to write the ugly hack that the lambda "compiler" creates.
Now it's time to make some simple yet important observations about variable scope in PHP. The main thing I want to bring attention to is function arguments. These tend to be rather benign things and we never think about how they affect scope--unless we are passing the arguments by reference. Most people also don't normally call their functions like this either...
<?php strtoupper($str = "hello"); var_dump($str); // output: string(5) "hello" ?>
The fact that the value of $str isn't changed to uppercase shouldn't be surprising. That's not important. However, the fact that $str exists after the function call is. Normally one wouldn't think twice about this simple happening, but for my closure script it is incredibly important. This means that if the program can find "->call(" and ")" then a function can be appended to this to bring the possibly changed variables into the parent scope when the function is called. This also gives the little complication of the return value of call(), but that requires a trivial workaround. Here's the end result:
// how it was: $lambda->call($arg); // what it becomes: $lambda->storeCall($arg)->extrAndCall( extract(ScopeStack::getInstance()->popScope(),EXTR_OVERWRITE) );
Yikes! Definitely not elegant but it works nonetheless. As I mentioned earlier, this is only a partial solution because I don't expect people to add this to the first lambda() call (because sub-lambdas have a cleaner format). Here are the results of this new solution in place:
$func = lambda(array(), '$new_test', ' static $test; if($test === NULL) { lambda({ $test = $new_test; })->call(); } else { echo $test; } '); $func->call('hello'); $func->call(''); // output: hello
Comments
-
I have been reading about closures in Javascript, and I think I understand a bit about their use, but can you give some sort of example of a practical need or purpose for a closure in PHP? In other words, I am looking for something that either cannot be done in PHP without closures, or that can be done significantly more easily with closures than without. I am talking about practical applications here, something that might be used on a real web site. Thank you.posted by Ralph on Apr 22, 2008 at 1:24am
-
Closures are both a convenience and an extremely powerful tool. Generally in PHP, there is the unfortunate mindset that everything that can be done with a closure can simply be done with a predefined function and then we can just pass the name of that function around. A common example of this would be any PHP function that accepts a callback: it takes the string name of a function and then applies it to something. In the most basic sense, we could instead create a function on-the-fly and pass it to that original function; however, clearly this doesn't add much value to closures. Instead, lets consider closures and their scope. In Javascript, if I define a variable outside of the function, then that function can access that variable. I can keep nesting functions and that (global) variable will still be accessible to the innermost depths. PHP, on the other hand, has a different way of handling globals. Best practice has global variables as being explicit, i.e. you need to use $GLOBALS or the "global" keyword. In its current state, we can observe that PHP's global keyword seems to look up to the parent scope, for example: $var = 123; function foo() { global $var; // $var is now accessible from within foo() } If this tradition were continued and anonymous functions were introduced, then a bit of annoying trampolining, as it were, would need to be done to have the same effect: $var = 123; function foo() { global $var; $anon = function() { global $var; // yay! $var is accessible. } } And so we see that in making use of one of trying to propagate variables down into lower lexical closures, we go against best practices, i.e. not to use global variables, and end up with some messy code. Clearly, in its current state, closures won't yield the nice benefit of scoped variables that we've come to expect from Javascript. So, what can we still rely on? First, lets look at the current state of PHP functions: function foo() { function bar() { // ... } } This is legal PHP, but it doesn't have the effect one might expect. Instead of bar() being local to foo, and only existing within foo, when foo is called bar becomes a globally accessible function. In this sense, PHP has failed to encapsulate bar within foo. One would expect this to change with the introduction of lexical closures. Thus far I've really made no argument for lexical closures/anonymous functions in PHP besides a minor fix to the way it encapsulates functions. Given this, WHY do I think adding closures to PHP is a good idea? In general, I think it would represent a shift in mindset and workings of PHP. Adding closures would require internal changes to how PHP works, unless closures just end up being syntactic sugar for create_function. The changes I'm talking about would mean that PHP would need to start recognizing functions in an entirely different way--that is, they would be first class, scoping would work in the way one would expect it to work in a language like javascript, etc. Almost all of this represents a dream for a better PHP, and most of it isn't even practical given how PHP works. And so, I will answer the first question by stating that closures would help PHP insofar as they would require it to get out of the hole that it's dug with create_function, using strings to call functions, and other such hacks. To answer your other question, about when closures would be nice to have and applicable to real-world coding is much simpler than you think. Given that PHP has only one namespace (this changes in newer versions), we obviously cannot have one function named the same as another. Also, with many algorithms, especially ones that are most easily understood with recursion, we often need more than one function to perform the operations, and thus we end up having to create our basic function, then a few other functions with a prefix of some sort or a way to identify them. Obviously, we could go the Java way and put all these multistep algorithms into a class, but I've always found that ugly. Consider quicksort and mergesort. PHP already provides these built-in, but whatever. Both these algorithms have distinct divide and conquer steps. It would be especially convenient, and very understandable if one could only do: function quicksort(..) { function divide(...) { } function conquer(...) { } ... } function mergesort(..) { function divide(...) { } function conquer(...) { } ... } Neither of these algorithms requires the abstraction of a class, but both of them benefit from the encapsulation of closures. Within (and only within) each main function, the closures are defined and are very obviously linked to the parent functions without the need to create all global functions with prefixes. I don't feel that the above answer will convince everyone, as prefixes or different function names, etc, are simply the accepted way of doing things. I do, however, feel that closures provide the programmer with a different (and I think better) way of approaching problems--which is to give the programmer the ability to work on subproblems and not have to deal with any annoying name conflicts. I also think that the changes that closures would require of PHP would be beneficial in general. So, regardless of if this has convinced you, or just exposed some of the inherent limitations of PHP's current setup, I think it's in your best interest to explore other languages (such as Javascript, Python, Ruby, etc) that support closures and get a feel for how it changes your workflow and your approach to problems. As side note, with closures, one might also expect partial application and proper currying ability, which would just be *sweet*.posted by Peter Goodman on Apr 22, 2008 at 5:41am
-
An interesting thing I've dreamed of is that PHP would make variables names not require the $. That is, both variable names and function names would be recognized in the same symbol table, thus allowing for the easy mixing of functions and variables that you will notice in languages such as Javascript that consider functions as first-class entities. To do this, it would seem that the PHP team would only have to make variables not require the $ as a prefix to their name, and simply add the $ to the allowed characters of a symbol. Technically nothing, in terms of symbol overlap, would happen error-wise, so this could be a fun change. But, I also don't expect them to do this, which is unfortunate.posted by Peter Goodman on Apr 22, 2008 at 5:48am
Comment