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

すごい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.instances.all._, cats.syntax.flatMap._
import cats._
import cats.instances.all._
import cats.syntax.flatMap._

scala> :paste
// Entering paste mode (ctrl-D to finish)
object Catnip {
  implicit class IdOp[A](val a: A) extends AnyVal {
    def some: Option[A] = Some(a)
  }
  def none[A]: Option[A] = None
}
import Catnip._

// Exiting paste mode, now interpreting.

defined object Catnip
import Catnip._

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> import cats.data.Xor
import cats.data.Xor

scala> val xorOfXor = Xor.right[String, Xor[String, Int]](Xor.right[String, Int](1))
xorOfXor: cats.data.Xor[String,cats.data.Xor[String,Int]] = Right(Right(1))

scala> xorOfXor.flatten
res2: cats.data.Xor[String,Int] = Right(1)

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 を返す。