在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也不能完全解决这个问题呢。