在PHP中实现FizzBuzz
看到这个地方我一下子就生气了,然后就写下了这篇文章…
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);
一个类似函数式的FizzBuzz写法,类似PHP的。
以下是解释。
由于PHP无法定义运算符,所以不得不使用全角函数来替代。
虽然外观上看起来像是运算符,但最终仍需要调用函数,括号也会增加。
在这里我写了一些通过f()进行函数柯里化的东西。
然后,虽然有一些函数,但是想要实现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));
或许的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也不能完全解决这个问题呢。