注意: アプリカティブ・ファンクターに興味があってこのページに飛んできた人は、まずは Semigroupal と Apply を読んでほしい。
Functors, Applicative Functors and Monoids:
Control.Applicative
モジュールにある型クラスApplicative
に会いに行きましょう!型クラスApplicative
は、2つの関数pure
と<*>
を定義しています。
Cats の Applicative
を見てみよう:
@typeclass trait Applicative[F[_]] extends Apply[F] { self =>
/**
* `pure` lifts any value into the Applicative Functor
*
* Applicative[Option].pure(10) = Some(10)
*/
def pure[A](x: A): F[A]
....
}
Apply
を拡張して pure
をつけただけだ。
LYAHFGG:
pure
は任意の型の引数を受け取り、それをアプリカティブ値の中に入れて返します。 … アプリカティブ値は「箱」というよりも「文脈」と考えるほうが正確かもしれません。pure
は、値を引数に取り、その値を何らかのデフォルトの文脈(元の値を再現できるような最小限の文脈)に置くのです。
A
の値を受け取り F[A]
を返すコンストラクタみたいだ。
import cats._, cats.syntax.all._
Applicative[List].pure(1)
// res0: List[Int] = List(1)
Applicative[Option].pure(1)
// res1: Option[Int] = Some(value = 1)
これは、Apply[F].ap
を書くときに {{...}.some}
としなくて済むのが便利かも。
{
val F = Applicative[Option]
F.ap({ F.pure((_: Int) + 3) })(F.pure(9))
}
// res2: Option[Int] = Some(value = 12)
Option
を抽象化したコードになった。
LYAHFGG:
では、「アプリカティブ値のリスト」を取って「リストを返り値として持つ1つのアプリカティブ値」を返す関数を実装してみましょう。これを
sequenceA
と呼ぶことにします。
sequenceA :: (Applicative f) => [f a] -> f [a]
sequenceA [] = pure []
sequenceA (x:xs) = (:) <$> x <*> sequenceA xs
これを Cats でも実装できるか試してみよう!
def sequenceA[F[_]: Applicative, A](list: List[F[A]]): F[List[A]] = list match {
case Nil => Applicative[F].pure(Nil: List[A])
case x :: xs => (x, sequenceA(xs)) mapN {_ :: _}
}
テストしてみよう:
sequenceA(List(1.some, 2.some))
// res3: Option[List[Int]] = Some(value = List(1, 2))
sequenceA(List(3.some, none[Int], 1.some))
// res4: Option[List[Int]] = None
sequenceA(List(List(1, 2, 3), List(4, 5, 6)))
// res5: List[List[Int]] = List(
// List(1, 4),
// List(1, 5),
// List(1, 6),
// List(2, 4),
// List(2, 5),
// List(2, 6),
// List(3, 4),
// List(3, 5),
// List(3, 6)
// )
正しい答えが得られた。興味深いのは結局 Applicative
が必要になったことと、
sequenceA
が型クラスを利用したジェネリックな形になっていることだ。
sequenceA
は、関数のリストがあり、そのすべてに同じ引数を食わして結果をリストとして眺めたい、という場合にはとても便利です。
Function1
の片側が Int
に固定された例は、型解釈を付ける必要がある。
{
val f = sequenceA[Function1[Int, *], Int](List((_: Int) + 3, (_: Int) + 2, (_: Int) + 1))
f(3)
}
// res6: List[Int] = List(6, 5, 4)
以下がの Applicative
のための法則だ:
pure id <*> v = v
pure f <*> pure x = pure (f x)
u <*> pure y = pure ($ y) <*> u
Cats はもう 1つ別の法則を定義している:
def applicativeMap[A, B](fa: F[A], f: A => B): IsEq[F[B]] =
fa.map(f) <-> fa.ap(F.pure(f))
F.ap
と F.pure
を合成したとき、それは F.map
と同じ効果を得られるということみたいだ。
結構長くなったけど、ここまでたどり着けて良かったと思う。続きはまたあとで。