1. Applicative

Applicative 

注意: アプリカティブ・ファンクターに興味があってこのページに飛んできた人は、まずは SemigroupalApply を読んでほしい。

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 を抽象化したコードになった。

Applicative の便利な関数 

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則 

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

  • identity: pure id <*> v = v
  • homomorphism: pure f <*> pure x = pure (f x)
  • interchange: 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.apF.pure を合成したとき、それは F.map と同じ効果を得られるということみたいだ。

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