LoginSignup
9
11

More than 5 years have passed since last update.

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

Posted at

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

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

9
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
11