http://itchyny.hatenablog.com/entry/2015/12/27/150000
ここを見てついカッとなって書いてしまった…。
function 〜>(...$args){
return f(function($d, $s, $n){
return <$($s, |($n % $d === 0));
}, ...$args);
}
$fizzbuzz = fromMaybe()->ap(<>(〜>(3, "Fizz"), 〜>(5, "Buzz")));
$result = MonadList::cast(range(1, 100))->fmap($fizzbuzz);
PHPの関数型っぽいFizzBuzz。
以下説明。
PHPでは演算子を定義できない。仕方がないので全角関数で代用する。
見かけは演算子っぽくなっても、結局関数呼び出しが必要でカッコが増えるけども。
f()で包むとカリー化される諸々は http://qiita.com/nishimura/items/f78c64b2c86c82a4af08 こっちで書いた。
で、ある程度の関数はあるけど、FizzBuzzしようと思ったら全然足りないんだよなあ…。
仕方がないので今回のために定義する。全角の疑似演算子で。
function <$(...$args){
$ret = fmap()->compose(cnst());
if ($args) $ret = $ret(...$args);
return $ret;
}
// test
var_dump(<$(1, Just(0)));
var_dump(<$(1, Nothing()));
var_dump(<$(1, MonadList::cast([1,2,3,4,5])));
function |(...$args){
return f(function($bool){
if ($bool) return Just(0);
else return Nothing();
}, ...$args);
}
function 〜>(...$args){
return f(function($d, $s, $n){
return <$($s, |($n % $d === 0));
}, ...$args);
}
// test
var_dump(〜>(3, "Fizz", 5));
var_dump(〜>(3, "Fizz", 6));
以前は mplus は書いたけど mappend は書いてない。
ある程度実用的に使おうと思ったら、Monad・MonadPlusの他に、Monoid・Alternativeも必要!?
オブジェクト指向に無理矢理関数型をくっつける限界が近づいてきたような。
とりあえずメソッドにせずに場当たり的な関数を書く。
function mappend(...$args){
return f(function($m1, $m2){
// Monoid String
if (is_string($m1) && is_string($m2)){
return $m1 . $m2;
}
// Monoid b => Monoid (a -> b)
if ($m1 instanceof Func &&
$m2 instanceof Func){
return f(function($a) use ($m1, $m2){
return mappend($m1($a), $m2($a));
});
}
// Monoid a => Monoid (Maybe a)
if ($m1 instanceof Maybe &&
$m2 instanceof Maybe){
if ($m1 == Nothing()){
return $m2;
}else if ($m2 == Nothing()){
return $m1;
}else{
return $m1->bind(function($v1) use ($m2){
return $m2->fmap(function($v2) use ($v1){
return mappend($v1, $v2);
});
});
}
}
}, ...$args);
}
function <>(...$args){
return mappend(...$args);
}
// test
var_dump(<>(Just('Fizz'), Just('Buzz')));
var_dump(<>(Just('Fizz'), Nothing()));
var_dump(<>(Nothing(), Just('Buzz')));
var_dump(<>(Nothing(), Nothing()));
var_dump(<>(〜>(3, "Fizz"), 〜>(5, "Buzz"), 9));
var_dump(<>(〜>(3, "Fizz"), 〜>(5, "Buzz"), 10));
var_dump(<>(〜>(3, "Fizz"), 〜>(5, "Buzz"), 15));
var_dump(<>(〜>(3, "Fizz"), 〜>(5, "Buzz"), 11));
Maybeのmappendはmplusと違って中身までmappendするのが異なるようだ。
ここまででほぼ完成したのでチェック。
$tmp = f(function($n){
return fromMaybe($n, <>(〜>(3, "Fizz"), 〜>(5, "Buzz"), $n));
});
var_dump($tmp(9));
var_dump($tmp(10));
var_dump($tmp(11));
var_dump($tmp(15));
var_dump(MonadList::cast(range(1, 100))->map($tmp));
あとは ポイントフリーにして、show的な関数を挟んで
$fizzbuzz = fromMaybe()->ap(<>(〜>(3, "Fizz"), 〜>(5, "Buzz")));
function pr(...$args){
return f(function($a){
echo $a, "\n";
return $a;
}, ...$args);
}
MonadList::cast(range(1, 100))->fmap(pr()->compose($fizzbuzz));
完成。
全体のソースをここに置いた。
https://github.com/nishimura/monad-fizzbuzz/blob/master/fizzbuzz.php
やっぱり適当な型クラスを書いて後からインスタンスにできるのは便利だよなあ。
PHPはスクリプト言語だしもうちょっとRubyやJavaScriptみたいに後からクラス定義に追加できても良さそうなもんだけども。
PHP7の無名クラスとtraitでもあまり解決できる感じがしないなあ。