Tuesday, December 2, 2008

closures in php

So here is the implementation of closures which my "language" (LET) makes use of. This would normally be another boring attempt at closures in php but I believe it is quite clean. First let me show you the code before transmogrification:


$func = let($x = 10){[$y | $x = $x*$y]};
echo $func y:2; #20
echo $func y:2; #40 (as x is now 20 having been modified by prior call to closure)

Notice the dropped brackets and keyword parameters? Also note the fact that a lambda by default returns the value of it's last statement, the let block is a form of lambda and thus also returns it's last value which is in this case the name of the generated function. Note that this is actually parsing the square lambda to determine if it makes use of any free variables, if so it creates a closure otherwise it will just create a "normal" function. Because of the fact I am parsing and then writing the new php file I can take my time instead of just dumping in get_defined_vars (holy waste of memory batman). Also note that if the square lambda were to have passed in $x as a variable "before the pipe" than it would NOT consider $x a free variable and thus not create a closure.

First the simple class for handling closures

class __Closure{
private $method;
private $env;
function __construct($method, $env){
$this->method = $method;
$this->env = $env;
}

function call(){
$args = func_get_args();
$args[] = $this->env;
return call_user_func_array($this->method,$args);
}
}


And now the transmogrified version of the let statement:


function lambda_closure_0($y,$env){
return $env['x'] * $y;
}

function lambda_func_0($y){
return $GLOBALS['lambda_closure_0']->call($y);
}

function lambda_let_block_0($x){
$GLOBALS['lambda_closure_0'] = new __Closure('lambda_func_0',Array('x'=>&$x));
return 'lambda_func_0';
}

$func = lambda_let_block_0(10);
echo $func(2); #20
echo $func(2); #40

The clever part of this whole thing is determining at parse time which "free" variable are being accessed by the square lambda and then passing them to a new instance of __Closure class by reference (thus: Array('x' => &$x)).

Notice that it is a LOT more code once the let block has been expanded by the tansmogrifier. This is the entire point of the project -expand to verbose and yet simple code which tries to make minimal use of any create_function calls.

When php 5.3 becomes "acceptable" and on all the servers I use then I will make the transmogrifier use the new "uses" clause with the inline function syntax but that's precisely the beauty of the transmogrifier - my syntax stays the same and the underlying php can be optimized instead. This is the art and beauty of meta programming.

No comments: