-
-
Save ajiyoshi-vg/b044ad6b0166410705de to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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