Applicative 

注意: アプリカティブ・ファンクターに興味があってこのページに飛んできた人は、まずは 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] を返すコンストラクタみたいだ。

scala> import cats._, cats.instances.all._
import cats._
import cats.instances.all._

scala> Applicative[List].pure(1)
res0: List[Int] = List(1)

scala> Applicative[Option].pure(1)
res1: Option[Int] = Some(1)

これは、Apply[F].ap を書くときに {{...}.some} としなくて済むのが便利かも。

scala> val F = Applicative[Option]
F: cats.Applicative[Option] = cats.instances.OptionInstances$$anon$1@3600b6f8

scala> F.ap({ F.pure((_: Int) + 3) })(F.pure(9))
res2: Option[Int] = Some(12)

Option を抽象化したコードになった。

Applicative の便利な関数 

LYAHFGG:

では、「アプリカティブ値のリスト」を取って「リストを返り値として持つ1つのアプリカティブ値」を返す関数を実装してみましょう。これを sequenceA と呼ぶことにします。

sequenceA :: (Applicative f) => [f a] -> f [a]  
sequenceA [] = pure []  
sequenceA (x:xs) = (:) <$> x <*> sequenceA xs  

これを Cats でも実装できるか試してみよう!

scala> import cats.syntax.cartesian._
import cats.syntax.cartesian._

scala> 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)) map {_ :: _} 
       }
sequenceA: [F[_], A](list: List[F[A]])(implicit evidence$1: cats.Applicative[F])F[List[A]]

テストしてみよう:

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> sequenceA(List(1.some, 2.some))
res3: Option[List[Int]] = Some(List(1, 2))

scala> sequenceA(List(3.some, none[Int], 1.some))
res4: Option[List[Int]] = None

scala> 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 に固定された例は、型解釈を付ける必要がある。

scala> val f = sequenceA[Function1[Int, ?], Int](List((_: Int) + 3, (_: Int) + 2, (_: Int) + 1))
f: Int => List[Int] = <function1>

scala> f(3)
res6: List[Int] = List(6, 5, 4)

Applicative則 

以下がの Applicative のための法則だ:

Cats はもう 1つ別の法則を定義している:

  def applicativeMap[A, B](fa: F[A], f: A => B): IsEq[F[B]] =
    fa.map(f) <-> fa.ap(F.pure(f))

F.apF.pure を合成したとき、それは F.map と同じ効果を得られるということみたいだ。

結構長くなったけど、ここまでたどり着けて良かったと思う。続きはまたあとで。