Posted at

PHPでカリー化、部分適用、関数合成、そしてMaybeモナドなど

More than 3 years have passed since last update.

最初は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

部分適用する場合、型推論がないので型をコメントに書くのは必須かもしれない。