closures in php
18 December 2006 // php. stuff.
Php is definitely not a suitable language for functional programming. Php functions live in a parallel reality - when your program starts, the construction of functions is complete, nothing can be changed anymore. However, Php is an open language (as it has eval ), so it should be possible to do some experiments.
Here the function that creates a closure.
It expects two arguments: vars is a hash that maps variables' names to values (as in compact), body is the code of resulting function. Within the body , $0, $1 etc. can be used as arguments placeholders.
function closure($vars, $body) {
global $_cvars;
if(!isset($_cvars))
$_cvars = array();
$len = count($_cvars);
$_cvars[$len] = $vars;
$argc = 0;
preg_replace('/\$(\d)/e', '$argc = max($argc, $1)', $body);
$args = '$_' . implode("='',\$_", range(0, $argc)) . "=''";
$expr = preg_replace('/(?<!\\\\)\\$(\d)/', '$_$1', $body);
return create_function($args, "
extract(\$GLOBALS['_cvars'][$len], EXTR_REFS);
$expr;
");
}
Example of using closure :
# fibonacci n-step number generator
# http://mathworld.wolfram.com/Fibonaccin-StepNumber.html
function fibonacci($n) {
$a = array(1);
return closure(compact('a', 'n'), '
$f = end($a);
$a[] = array_sum($a);
if(--$n <= 0)
array_shift($a);
return $f;
');
}
# ordinary fibonacci numbers
$f = fibonacci(2);
for($i = 0; $i < 20; $i++)
echo $f(),' ';
# tetranacci numbers
$f = fibonacci(4);
for($i = 0; $i < 20; $i++)
echo $f(),' ';
Calling compact and typing-in lists of variables can become tedious, therefore there's another interface that is able to discover variables automatically. The price is that you must use eval with it.
function cc($body) {
$body = addcslashes($body, "'\\");
$v = array();
preg_replace('/(?<!\\\\)\\$([a-zA-Z]\w*)/e',
'$v[]="$1"', $body);
if(!count($v))
return "return closure(array(), '$body');";
$v = "'" . implode("','", array_unique($v)) . "'";
return "return closure(compact($v), '$body');";
}
The usage is eval(cc('function body')) . Note that cc is not very accurate about parsing, fancy variable-variables syntax will probably confuse it. Præmonitus, præmunitus. ;)
cc example:
function file_reader($filename) {
$fp = fopen($filename, "r");
return eval(cc('
$line = fgets($fp);
if(FALSE === $line)
fclose($fp);
return $line;
'));
}
function sql_reader($query, $separator) {
$qq = mysql_query($query);
return eval(cc('
$rec = mysql_fetch_row($qq);
if(!$rec) {
mysql_free_result($qq);
return FALSE;
}
return implode($separator, $rec);
'));
}
function line_printer($reader) {
$n = 0;
while(FALSE !== ($line = $reader())) {
$n++;
print "line $n: $line<br>\n";
}
}
# lets print a file, line-by-line
line_printer(file_reader(__FILE__));
# lets print a mysql table
line_printer(sql_reader('SHOW VARIABLES', '='));
Peter Goodman :
I worked on a similar concept a little while ago. You can check it out at: http://ioreader.../. The code is at: http://ioreader.... My approach was to actually compile the inner lambdas to PHP instead of eval'ing them.
comment on this