この節では、モナド値を操作したり、モナド値を返したりする関数(両方でも可!)をいくつか紹介します。そんな関数はモナディック関数と呼ばれます。
Haskell 標準の Monad
と違って Cats の Monad
は後知恵である
より弱い型クラスを用いた粒度の高い設計となっている。
Monad
FlatMap
and Applicative
Apply
Functor
そのため、全てのモナドがアプリカティブ・ファンクターとファンクターであることは自明となっていて、
モナドを形成する全てのデータ型に対して ap
や map
演算子を使うことができる。
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
を実装するので、
これを抽象型にするために抽象関数を書く必要がある。
import cats._, cats.syntax.all._
def join[F[_]: FlatMap, A](fa: F[F[A]]): F[A] =
fa.flatten
join(1.some.some)
// res0: Option[Int] = Some(value = 1)
どうせ関数にしてしまうのなら、関数構文をそのまま使えばいい。
FlatMap[Option].flatten(1.some.some)
// res1: Option[Int] = Some(value = 1)
LYAHFGG:
Control.Monad
モジュールのfilterM
こそ、まさにそのための関数です! … 述語はBool
を結果とするモナド値を返しています。
Cats では filterM
を提供しないが、TraverseFilter に filterA
がある。
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)))
使ってみる。
def binSmalls(acc: Int, x: Int): Option[Int] =
if (x > 9) none[Int] else (acc + x).some
(Foldable[List].foldM(List(2, 8, 3, 1), 0) {binSmalls})
// res2: Option[Int] = Some(value = 14)
(Foldable[List].foldM(List(2, 11, 3, 1), 0) {binSmalls})
// res3: Option[Int] = None
上の例では binSmals
が 9 より多きい数を見つけると None
を返す。