最初はMaybeモナドだけ作ろうと思っていたところ、fmapも欲しいし、そうするとFunctorかなー、fmapがあるなら <$> <*> もあった方がいいかなー、それだとFunctor => Applicative => Monad => Maybe にした方がいいなー、とか考えてこうなった。
class Curry
{
protected $value;
public function __construct(callable $f)
{
$this->value = self::curry($f);
}
public static function curry(callable $f)
{
$ref = new \ReflectionFunction($f);
$count = $ref->getNumberOfParameters();
if ($count === 0)
throw new \InvalidArgumentException('Cannot curry none arguments function');
$args = array_fill(0, $count, null);
$prev = function($a) use ($f, &$args, $count){
$args[$count - 1] = $a;
return call_user_func_array($f, $args);
};
for ($i = $count - 2; $i >= 0; $i--){
$prev = new static(function($a) use ($prev, &$args, $i){
$args[$i] = $a;
return $prev;
});
}
return $prev;
}
public function __invoke($a)
{
if ($this->value instanceof Curry){
$count = 1;
}else if (is_callable($this->value)){
$ref = new \ReflectionFunction($this->value);
$count = $ref->getNumberOfParameters();
}
$args = func_get_args();
$params = [];
for ($i = 0; $i < $count; $i ++){
$params[] = array_shift($args);
}
$ret = call_user_func_array($this->value, $params);
if ($args){
return call_user_func_array($ret, $args);
}else{
if (is_callable($ret) && !($ret instanceof Curry))
return new static($ret);
else
return $ret;
}
}
/*
* alias of __invoke
*/
public function apply()
{
return call_user_func_array($this, func_get_args());
}
public function compose(callable $f)
{
return new static(function ($a) use ($f){
return $f(call_user_func_array($this, func_get_args()));
});
}
}
一応検索して
http://qiita.com/kumazo@github/items/3c337c6d51f023ae59ac
http://qiita.com/yuya_takeyama/items/e14758907ca905386114
この辺のカリー化のコードを見たんだけども、どうも関数実行時にうまいこと帳尻を合わせている感がある。これだとせっかくカリー化しても合成するときにうまくいかないんじゃないかな?と思って引数ひとつの関数に愚直に分解した。
そしてモナド。
OOPとしてMaybeの実装は色々あるけれど、たぶん重要なのは引数の順番と部分適用。
$result1 = $maybeValue1->maybe($default, $callback);
$result2 = $maybeValue2->maybe($default, $callback);
$result3 = $maybeValue3->maybe($default, $customCallback);
これでもいいんだけど
$withDefault = maybe($default);
$withCallback = $withDefault($callback);
$result1 = $withCallback($maybeValue1);
$result2 = $withCallback($maybeValue2);
$result3 = $withDefault($customCallback, $maybeValue3);
が本来の使い方じゃないだろうか。
そんなわけで部分適用 や fmap を実装したモナドを書いた。
https://github.com/nishimura/laiz-monad
部分適用する場合、型推論がないので型をコメントに書くのは必須かもしれない。