Skip to content

Instantly share code, notes, and snippets.

@ajiyoshi-vg
Last active August 29, 2015 13:57
Show Gist options
  • Save ajiyoshi-vg/b044ad6b0166410705de to your computer and use it in GitHub Desktop.
Save ajiyoshi-vg/b044ad6b0166410705de to your computer and use it in GitHub Desktop.
<?php
/*
* PHPのクロージャは、JavaScriptのクロージャのように作ってすぐ呼び出せないのを何とかしたかった。
*
* 例えばJavaScriptでは以下のように、作ったクロージャをその場で呼び出すことができる。
* (function(a) { return a; })(1);
* ↑と同じ効果を得るために、
* curry(function($a) { return $a; })->_(1);
* と書けるようにした
*
* 変数として一旦受けたら、関数呼び出し構文でも呼べる。
*
* $c = curry(function($a) { return $a; });
* $c(10); # → 10;
*/
class Curry {
public function __construct($n, $f) {
$this->n = $n;
$this->f = $f;
}
public function _() {
$f = $this->f;
$num = func_num_args();
$arg = func_get_args();
if( $this->n === $num || $this->n < $num){
return call_user_func_array($f, $arg);
} else {
return new Curry($this->n - $num, function() use ($arg, $f) {
$full_args = array_merge($arg, func_get_args());
return call_user_func_array($f, $full_args);
});
}
}
public function __invoke(){
return call_user_func_array(array($this, '_'), func_get_args());
}
public function __toString(){
return Curry::var_dump_str($this);
}
public static function num_args($closure){
$s = Curry::var_dump_str($closure);
if( $closure instanceof Curry ){
return $closure->n;
}else if( preg_match('/\["parameter"\]=>.*array\(([0-9]+)\)/s', $s, $out) ){
return (int)$out[1];
} else {
try {
$f = new ReflectionFunction($closure);
return $f->getNumberOfParameters();
} catch(Exception $e) {
throw new Exception("何引数か分かりませんでした($s)");
}
}
}
public static function var_dump_str($obj){
ob_start();
var_dump($obj);
$ret = ob_get_contents();
ob_end_clean();
return $ret;
}
}
function curry($f) {
$n = Curry::num_args($f);
return new Curry($n, $f);
}
echo curry(function($a, $b, $c) { return $a * $b * $c; })->_(1)->_(2)->_(3), "\n";
echo curry(function($a, $b, $c) { return $a * $b * $c; })->_(1, 2, 3), "\n";
echo curry(function($a, $b, $c) { return $a * $b * $c; })->_(1, 2)->_(3), "\n";
echo curry(function($a, $b, $c) { return $a * $b * $c; })->_(1, 2, 3, 4), "\n";
echo curry(function($a, $b, $c) { return $a * $b * $c; })->_(1)->_(2, 3, 4), "\n";
$x = 10;
echo curry(function($a, $b) use($x) { return $x * $a + $b; })->_(2)->_(3), "\n";
echo curry('implode')->_(",", array(1, 2, 3)), "\n";
echo curry('implode')->_(",")->_(array(1, 2, 3)), "\n";
$c = curry(function($a, $b){ return $a + $b; });
echo $c->_(2)->_(3), "\n";
echo $c->_(2, 3), "\n";
echo $c(2, 3), "\n";
$d = $c->_(2);
echo $d->_(3), "\n";
echo $d(3), "\n";
/*
* ここからが本題。
*
* そもそもやりたかったことはYコンビネータの実装であったのだが、
* PHPの構文だとλ式を作ってもすぐには呼べないので辛かった。
* curry#_() の力を借りて Yコンビネータを実装する
*/
function Y($f) {
return curry(function($y) use ($f) {
return function($x) use ($f, $y) { return curry($f($y($y)))->_($x); };
})->_(function($y) use ($f) {
return function($x) use ($f, $y) { return curry($f($y($y)))->_($x); };
});
};
echo curry(Y(function($f){
return function($x) use ($f) {
return ($x === 1) ? 1 : $x * $f($x-1);
};
}))->_(5), "\n";
/*
* 完全なコンビネータ(=自由変数が存在しないラムダ式)による再帰プログラム
*/
echo curry(curry(function($f) {
return curry(function($y) use ($f) {
return function($x) use ($f, $y) { return curry($f($y($y)))->_($x); };
})->_(function($y) use ($f) {
return function($x) use ($f, $y) { return curry($f($y($y)))->_($x); };
});
})->_(function($f) {
return function($x) use($f) {
return ($x===1) ? 1 : $x * $f($x-1);
};
}))->_(5), "\n";
/*
* 途中のλ式を、複数の引数を取るラムダ式をcurryを使って展開する。
* さらに変数に入れちゃうことにより見通しを良くする
* (説明のための何か)
*/
function YComb($f) {
$yc = curry(function($y, $x) use ($f) {
return $f( $y($y), $x );
});
return $yc($yc);
}
$fib = YComb(curry(function($f, $x){
return $x < 2 ? $x : $f($x-1) + $f($x-2);
}));
echo $fib(6), "\n";
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment