RSS Feed

Peter Goodman's blog about PHP, Parsing Theory, C++, Functional Programming, Applications,

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


Comment