便利なモナディック関数特集 

すごいHaskellたのしく学ぼう 曰く:

この節では、モナド値を操作したり、モナド値を返したりする関数(両方でも可!)をいくつか紹介します。そんな関数はモナディック関数と呼ばれます。

Haskell 標準の Monad と違って Cats の Monad は後知恵である より弱い型クラスを用いた粒度の高い設計となっている。

そのため、全てのモナドがアプリカティブ・ファンクターとファンクターであることは自明となっていて、 モナドを形成する全てのデータ型に対して apmap 演算子を使うことができる。

flatten メソッド 

LYAHFGG:

実は、任意の入れ子になったモナドは平らにできるんです。そして実は、これはモナド特有の性質なのです。このために、join という関数が用意されています。

Cats でこれに当たる関数は flatten と呼ばれており、FlatMap にて定義されている。 simulacrum のお陰で flatten はメソッドとしても導入されている。

@typeclass trait FlatMap[F[_]] extends Apply[F] {
  def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B]

  /**
   * also commonly called join
   */
  def flatten[A](ffa: F[F[A]]): F[A] =
    flatMap(ffa)(fa => fa)

  ....
}

Option[A] は既に flatten を実装するので、 これを抽象型にするために抽象関数を書く必要がある。

scala> import cats._, cats.data._, cats.implicits._
import cats._
import cats.data._
import cats.implicits._

scala> def join[F[_]: FlatMap, A](fa: F[F[A]]): F[A] =
         fa.flatten
join: [F[_], A](fa: F[F[A]])(implicit evidence$1: cats.FlatMap[F])F[A]

scala> join(1.some.some)
res0: Option[Int] = Some(1)

どうせ関数にしてしまうのなら、関数構文をそのまま使えばいい。

scala> FlatMap[Option].flatten(1.some.some)
res1: Option[Int] = Some(1)

Xor 値の Xor に対して flatten メソッドを使おうと思ったけど、うまくいかなかった:

scala> val xorOfXor = Xor.right[String, Xor[String, Int]](Xor.right[String, Int](1))
<console>:20: error: not found: value Xor
       val xorOfXor = Xor.right[String, Xor[String, Int]](Xor.right[String, Int](1))
                      ^
<console>:20: error: not found: type Xor
       val xorOfXor = Xor.right[String, Xor[String, Int]](Xor.right[String, Int](1))
                                        ^
<console>:20: error: not found: value Xor
       val xorOfXor = Xor.right[String, Xor[String, Int]](Xor.right[String, Int](1))
                                                          ^

scala> xorOfXor.flatten
<console>:21: error: not found: value xorOfXor
       xorOfXor.flatten
       ^

filterM メソッド 

LYAHFGG:

Control.Monad モジュールの filterM こそ、まさにそのための関数です! … 述語は Bool を結果とするモナド値を返しています。

Cats では filterM を提供しないが、TraverseFilterfilterA がある。

foldM 関数 

LYAHFGG:

foldl のモナド版が foldM です。

Cats には foldM が無いみたいだったので、自分で定義してみたけどもスタック・セーフじゃなかった。Tomas Mikula がそれを修正した実装を追加してくれて、それが #925 でマージされた。

  /**
   * Left associative monadic folding on `F`.
   */
  def foldM[G[_], A, B](fa: F[A], z: B)(f: (B, A) => G[B])(implicit G: Monad[G]): G[B] =
    foldLeft(fa, G.pure(z))((gb, a) => G.flatMap(gb)(f(_, a)))

使ってみる。

scala> def binSmalls(acc: Int, x: Int): Option[Int] =
         if (x > 9) none[Int] else (acc + x).some
binSmalls: (acc: Int, x: Int)Option[Int]

scala> (Foldable[List].foldM(List(2, 8, 3, 1), 0) {binSmalls})
res3: Option[Int] = Some(14)

scala> (Foldable[List].foldM(List(2, 11, 3, 1), 0) {binSmalls})
res4: Option[Int] = None

上の例では binSmals が 9 より多きい数を見つけると None を返す。

Contents