PHPをHaskellっぽくする (続：FizzBuzz)

# ハードル１：オブジェクト指向の設計方針と合わない

オブジェクト指向ではデータと操作をひとまとまりにして定義する。
これをベースに関数型を移植しようとすると、次の二点で結構つらい。

- クラス実装が大きくなる
- 後からインスタンスを追加できない

例えばFunctorやApplicativeなどを色々インポートした状態でMaybeを調べると

```hs
*Main> :i Maybe
data Maybe a = Nothing | Just a 	-- Defined in `Data.Maybe'
instance Eq a => Eq (Maybe a) -- Defined in `Data.Maybe'
instance Monad Maybe -- Defined in `Data.Maybe'
instance Functor Maybe -- Defined in `Data.Maybe'
instance Ord a => Ord (Maybe a) -- Defined in `Data.Maybe'
instance Read a => Read (Maybe a) -- Defined in `GHC.Read'
instance Show a => Show (Maybe a) -- Defined in `GHC.Show'
instance MonadFix Maybe -- Defined in `Control.Monad.Fix'
instance MonadPlus Maybe -- Defined in `Control.Monad'
instance Applicative Maybe -- Defined in `Control.Applicative'
instance Monoid a => Monoid (Maybe a) -- Defined in `Data.Monoid'
instance Alternative Maybe -- Defined in `Control.Applicative'
```

こんな感じでずらずらっと表示される。
オブジェクト指向的には、Maybeに対する操作はMaybeに実装すべきなんだろうけども、これを全部実装していくとimplementsとかtraitが大変なことになる。
EqやShowなど使わないものを抜いたとしても、インターフェースが多すぎてなんか違う感が半端ない。

他にも、fmapは

```hs
*Main> :t fmap
fmap :: Functor f => (a -> b) -> f a -> f b
```

なんだけども、オブジェクト指向で普通に書くと第一引数と第二引数が逆転する。Scalaでもそうなっている。
これが関数を貼り合せるのに向いていない。


# ハードル２：型推論がないとメソッドが呼び出せない

端的に言うと、pureやmemptyを実装できない。
mempty >>= bind とかは更にキツイ。

例えばguard関数を書こうとしてHaskellの定義を見て

```hs
guard           :: (Alternative f) => Bool -> f ()
guard True      =  pure ()
guard False     =  empty
```

詰まる。
これが例えばap(Applicativeの<*>)なら

```hs
ap                :: (Monad m) => m (a -> b) -> m a -> m b
```

引数で型が決まるので、第一引数または第二引数のクラスの ap を呼びだせば良い。
それが guard だと具体的な型がまったく分からない。


# ハードル３：スカラー型を使いたい

arrayのモナド版 MonadArray を作ったり、文字列のモナド版 MonadString を作ったりしたくない。
巷で不評のPHP版arrayだけども、ガッチリ制限の付いたMonadArrayを作るよりゆるいPHP版arrayを使ってfmapとかmappendとかしたい。




# 回避１：型クラスに対応するクラスを分ける

そもそもPHPにJavaScriptやRubyのような柔軟性があれば問題なかったんだけども、スクリプト言語よりもむしろJavaっぽいので仕方がない。
仕方がないのでデータ型と型クラス階層を分ける。

```php
/*
 * data Maybe a = Nothing | Just a
 */
class Maybe
{
    protected $value;
    public function __construct($a)
    {
        $this->value = $a;
    }
}

namespace Maybe;
use Maybe;

class Just extends Maybe
{
    public function fromJust()
    {
        return $this->value;
    }
}
class Nothing extends Maybe
{
    public function __construct($a) {}
}
```

```php
/*
 * class Monad m where
 */
interface Monad extends Applicative
{
    public static function bind($m, callable $f);
    public static function ret($a);
}

namespace Monad;

use Applicative;
use Monad;
use Maybe as Instance;

/*
 * instance Monad Maybe
 */
class Maybe extends Applicative\Maybe implements Monad
{
    public static function bind($m, callable $f)
    {
        assert($m instanceof Instance, 'First argument must be Maybe');

        if ($m instanceof Instance\Just)
            $ret = $f($m->fromJust());
        else
            $ret = $m;

        assert($ret instanceof Instance, 'Return value must be Maybe');
        return $ret;
    }

    public static function ret($a)
    {
        return new Instance\Just($a);
    }
}
```

これで、後からMonoidやMonadPlusに対応させようとしても大本のMaybeをいじらなくて済む。
ついでに arrayやstring型でも対応できる。
その代わり、Haskell風型クラスのインスタンスを探すための処理が必要になる。


# 回避２：何かの型だよクラスを作る

PHP5の戻り値の型が書けない状態では、型推論に近づけるためのうまい方法を思い付けなかったので、何でもありクラスを作る。

```php
class Any
{
    ...

    public function castByName($name){
        ....
    }
    public function cast($m){
        ....
    }
}
```

実際の型が必要になったら、そのときに何とかする。
何ともできなかったらエラーになるんだけど。

途中まで作って、結局Scalaっぽくなるのかと気付いた。


# 実装サンプル

例によって https://github.com/nishimura/laiz-monad ここに置いた。

```php
var_dump(fromMaybe('NONE', ret('a')));
// "a"
var_dump(fromMaybe('NONE', mempty()));
// "NONE"

$a = ret('a');
var_dump(mappend($a, []));
// ['a']
var_dump(mappend($a, ''));
// 'a'
var_dump(mappend($a, Nothing()));
// Just 'a'
var_dump(mappend($a, Just('b')));
// Just 'ab'
```

このように動く。



もう一度 [PHPでFizzBuzz](http://qiita.com/nishimura/items/14fe1ede66aadc9df625)



これは実は完全ではなかった。
guard関数の中に直接 Just と Nothing を書いている。これではMaybe専用のguardだ。

今回でやっと正しいguardのfizzbuzzが書ける。

```php
<?php
require 'vendor/autoload.php';

function calc(...$args){
    return f(function($d, $s, $n){
        return fconst($s, guard($n % $d === 0));
    }, ...$args);
}

$fizzbuzz = fromMaybe()->ap(mappend(calc(3, "Fizz"), calc(5, "Buzz")));

function pr(...$args){
    return f(function($a){
        echo $a, "\n";
        return $a;
    }, ...$args);
}

$ret = fmap(pr()->compose($fizzbuzz), range(1, 100));
```

前回は MonadList::cast(range(1, 100)) とか書いてPHPのarrayをMonadListに変換してから実行していたが、今回はそれも要らない。