型クラスでHaskellのguardを定義してみる(未完)

@mzpさん主催の非公式名古屋Scalaに行ってきました。
前半は@yoshihiro503さんによるモナド講座。後半はCoq、F#、AmazonEC2、MessagePackなど「り、りろんはしってる」な話が飛び交ってました。

モナド講座のなかでfor文のifはfilterが必要だったり、先頭に置けなかったりでイケてない、って話でした。
一方Haskellのdoではguardっていうのが使えてこれが良い、Scalaでもがんばればそれっぽいものはできる、ということだったのでちょっと頑張ってみました。

結論から書くとあまりうまくはいきませんでした。どなたかアドバイスください…。

Scalaでguardを作る

ゴールとしてはこうなってほしい。

scala> for(a <- List(1,2,3,4); _ <- guard(a%2 == 1)) yield a
res0: List[Int] = List(1, 3)


Haskellでのguardはこう定義されている。

guard           :: (MonadPlus m) => Bool -> m ()
guard True      =  return ()
guard False     =  mzero

これをScalaに持込みたい。当初Monadの扱いがよくわからなかったが、SOでのMonadの話@kmizuさんのdefault(T)を作る話型クラス襲来を見ているうちに、型クラスをいうものを使えばうまくいきそうだと思い至った。

まずはMonadPlusを定義する。

trait MonadPlus[M[_]]{
  def unit[A](a:A): M[A]
  //def bind ...

  def mzero[A]: M[A]
  //def mplus ...
}

bindとmplusは今回使わないので省略。

そしてguardを定義。implicit parameter以外はHaskellでの定義と同じ。

def guard[A[_]](exp:Boolean)(implicit m:MonadPlus[A]) = if(exp)m.unit(()) else m.mzero

implicit parameterに入るのはこれ。

implicit object MonadicOption extends MonadPlus[Option]{
  def unit[A](a: A) = Some(a)
  def mzero[A] = None
}

guardを使ってみる

実際にfor文でguardを使ってみる

scala> for(_ <- guard(true); b <- Some("test")) yield b
res1: Option[java.lang.String] = Some(test)

scala> for(_ <- guard(false); b <- Some("test")) yield b
res2: Option[java.lang.String] = None

いい感じ。しかし、

scala> for(_ <- guard(true); b <- List(1,2,3)) yield b
<console>:9: error: type mismatch;
 found   : List[Int]
 required: Option[?]
       for(_ <- guard(true); b <- List(1,2,3)) yield b

ならばと以下を定義

implicit object MonadicList extends MonadPlus[List]{
  def unit[A](a: A) = List(a)
  def mzero[A] = Nil
}

あらためて実行。

scala> for(_ <- guard(true); b <- List(1,2,3)) yield b
<console>:10: error: ambiguous implicit values:
 both object MonadicList in object $iw of type object MonadicList
 and object MonadicOption in object $iw of type object MonadicOption
 match expected type MonadPlus[A]
       for(_ <- guard(true); b <- List(1,2,3)) yield b
                     ^

む、無念…。ちなみに型を指定すればいける。

scala> for(_ <- guard[List](true); b <- List(1,2,3)) yield b
res5: List[Int] = List(1, 2, 3)

scala> for(_ <- guard[List](false); b <- List(1,2,3)) yield b
res6: List[Int] = List()

scala> for(a <- List(1,2,3,4); _ <- guard[List](a%2 == 1)) yield a
res7: List[Int] = List(1, 3)

いろいろ試してみたけどついに型指定を消すことはできなかった…