今更だけれど、ネット巡回していて気付いた。
Functor, Applicative で説明したように -> は中置演算子で、関数の型は (->) r a と書くこともできます。(->) r を型コンストラクタと考えると、関数のモナドの定義を導くことができます。
instance Monad ((->) r) where return = const f >>= k = \ r -> k (f r) r
というわけで、PHPのカリー化関数クラスはこうなった。(一部省略)
<?php
class Func extends Curry implements Monad
{
use MonadTrait;
public function __construct(callable $f)
{
$this->value = static::curry($f);
}
// Functor
public function fmap(callable $f)
{
return $this->compose($f);
}
// Applicative
public static function pure($a)
{
return new static($a);
}
// Applicative
public function ap(Applicative $f)
{
assert($f instanceof static);
return $this->fmap($f);
}
// Monad
public static function ret($value)
{
// const
return new static(function($_) use ($value){
return $value;
});
}
// Monad
public static function fail($msg = null)
{
throw new \RuntimeException($msg);
}
// Monad
public function bind(callable $f)
{
return new static(function($a) use ($f){
if (!($f instanceof self))
$f = new self($f);
return $f($this($a), $a);
});
}
}
データベースとかファイルとか何らかのリソースを操作するクラスがあるとする。
それに対して、何らかの処理をする関数がいくつかある。
function getUser($userId, $db)
{
return $db->getUser($userId);
}
function getFriends($user, $db)
{
return $db->getAllFriends($user->myTeams);
}
function getFavorites($friends, $db)
{
return $db->getFavorites($friends);
}
この場合、各関数のカリー化されたものがあるとすると
$combine = $getUser($userId)
->bind($getFriends)
->bind($getFavorites);
$friendFavs = $combine($db);
関数をbindした後にdbを渡すと、全ての関数に連動してdbが行き渡る。
引数の順番が重要。
f >>= k = \ r -> k (f r) r
f と k を bind で繋げると、最後に適用する r が f と k にも適用されるようになっている。