Functors, Applicative Functors and Monoids:
ここまではファンクター値を写すために、もっぱら 1 引数関数を使ってきました。では、2 引数関数でファンクターを写すと何が起こるでしょう?
import cats._, cats.syntax.all._
{
val hs = Functor[List].map(List(1, 2, 3, 4)) ({(_: Int) * (_:Int)}.curried)
Functor[List].map(hs) {_(9)}
}
// res0: List[Int] = List(9, 18, 27, 36)
LYAHFGG:
では、ファンクター値
Just (3 *)
とファンクター値Just 5
があったとして、Just (3 *)
から関数を取り出してJust 5
の中身に適用したくなったとしたらどうしましょう?
Control.Applicative
モジュールにある型クラスApplicative
に会いに行きましょう!型クラスApplicative
は、2つの関数pure
と<*>
を定義しています。
Cats は Applicative
を Apply
と Applicative
に分けている。以下が Apply
のコントラクト:
/**
* Weaker version of Applicative[F]; has apply but not pure.
*
* Must obey the laws defined in cats.laws.ApplyLaws.
*/
@typeclass(excludeParents = List("ApplyArityFunctions"))
trait Apply[F[_]] extends Functor[F] with Cartesian[F] with ApplyArityFunctions[F] { self =>
/**
* Given a value and a function in the Apply context, applies the
* function to the value.
*/
def ap[A, B](ff: F[A => B])(fa: F[A]): F[B]
....
}
Apply
は Functor
、Cartesian
、そして ApplyArityFunctions
を拡張することに注目してほしい。
<*>
関数は、Cats の Apply
では ap
と呼ばれる。(これは最初は apply
と呼ばれていたが、ap
に直された。+1)
LYAHFGG:
<*>
はfmap
の強化版なのです。fmap
が普通の関数とファンクター値を引数に取って、関数をファンクター値の中の値に適用してくれるのに対し、<*>
は関数の入っているファンクター値と値の入っているファンクター値を引数に取って、1つ目のファンクターの中身である関数を2つ目のファンクターの中身に適用するのです。
LYAHFGG:
Applicative
型クラスでは、<*>
を連続して使うことができ、 1つだけでなく、複数のアプリカティブ値を組み合わせて使うことができます。
以下は Haskell で書かれた例:
ghci> pure (-) <*> Just 3 <*> Just 5
Just (-2)
Cats には apply 構文というものがある。
(3.some, 5.some) mapN { _ - _ }
// res1: Option[Int] = Some(value = -2)
(none[Int], 5.some) mapN { _ - _ }
// res2: Option[Int] = None
(3.some, none[Int]) mapN { _ - _ }
// res3: Option[Int] = None
これは Option
から Cartesian
が形成可能であることを示す。
LYAHFGG:
リスト(正確に言えばリスト型のコンストラクタ
[]
)もアプリカティブファンクターです。意外ですか?
apply 構文で書けるかためしてみよう:
(List("ha", "heh", "hmm"), List("?", "!", ".")) mapN {_ + _}
// res4: List[String] = List(
// "ha?",
// "ha!",
// "ha.",
// "heh?",
// "heh!",
// "heh.",
// "hmm?",
// "hmm!",
// "hmm."
// )
*>
と <*
演算子 Apply
は <*
と *>
という 2つの演算子を可能とし、これらも Apply[F].map2
の特殊形だと考えることができる。
定義はシンプルに見えるけども、面白い効果がある:
1.some <* 2.some
// res5: Option[Int] = Some(value = 1)
none[Int] <* 2.some
// res6: Option[Int] = None
1.some *> 2.some
// res7: Option[Int] = Some(value = 2)
none[Int] *> 2.some
// res8: Option[Int] = None
どちらか一方が失敗すると、None
が返ってくる。
次にへ行く前に、Optiona
値を作るために Cats が導入する syntax をみてみる。
9.some
// res9: Option[Int] = Some(value = 9)
none[Int]
// res10: Option[Int] = None
これで (Some(9): Option[Int])
を 9.some
と書ける。
これを Apply[Option].ap
と一緒に使ってみる:
import cats._, cats.syntax.all._
Apply[Option].ap({{(_: Int) + 3}.some })(9.some)
// res12: Option[Int] = Some(value = 12)
Apply[Option].ap({{(_: Int) + 3}.some })(10.some)
// res13: Option[Int] = Some(value = 13)
Apply[Option].ap({{(_: String) + "hahah"}.some })(none[String])
// res14: Option[String] = None
Apply[Option].ap({ none[String => String] })("woot".some)
// res15: Option[String] = None
どちらかが失敗すると、None
が返ってくる。
昨日の simulacrum を用いた独自型クラスの定義で見たとおり、 simulacrum は型クラス・コントラクト内で定義された関数を演算子として (魔法の力で) 転写する。
({(_: Int) + 3}.some) ap 9.some
// res16: Option[Int] = Some(value = 12)
({(_: Int) + 3}.some) ap 10.some
// res17: Option[Int] = Some(value = 13)
({(_: String) + "hahah"}.some) ap none[String]
// res18: Option[String] = None
(none[String => String]) ap "woot".some
// res19: Option[String] = None
LYAHFGG:
Control.Applicative
にはliftA2
という、以下のような型を持つ関数があります。
liftA2 :: (Applicative f) => (a -> b -> c) -> f a -> f b -> f c .
Scala ではパラメータが逆順であることを覚えているだろうか。
つまり、F[B]
と F[A]
を受け取った後、(A, B) => C
という関数を受け取る関数だ。
これは Apply
では map2
と呼ばれている。
@typeclass(excludeParents = List("ApplyArityFunctions"))
trait Apply[F[_]] extends Functor[F] with Cartesian[F] with ApplyArityFunctions[F] { self =>
def ap[A, B](ff: F[A => B])(fa: F[A]): F[B]
def productR[A, B](fa: F[A])(fb: F[B]): F[B] =
map2(fa, fb)((_, b) => b)
def productL[A, B](fa: F[A])(fb: F[B]): F[A] =
map2(fa, fb)((a, _) => a)
override def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] =
ap(map(fa)(a => (b: B) => (a, b)))(fb)
/** Alias for [[ap]]. */
@inline final def <*>[A, B](ff: F[A => B])(fa: F[A]): F[B] =
ap(ff)(fa)
/** Alias for [[productR]]. */
@inline final def *>[A, B](fa: F[A])(fb: F[B]): F[B] =
productR(fa)(fb)
/** Alias for [[productL]]. */
@inline final def <*[A, B](fa: F[A])(fb: F[B]): F[A] =
productL(fa)(fb)
/**
* ap2 is a binary version of ap, defined in terms of ap.
*/
def ap2[A, B, Z](ff: F[(A, B) => Z])(fa: F[A], fb: F[B]): F[Z] =
map(product(fa, product(fb, ff))) { case (a, (b, f)) => f(a, b) }
def map2[A, B, Z](fa: F[A], fb: F[B])(f: (A, B) => Z): F[Z] =
map(product(fa, fb))(f.tupled)
def map2Eval[A, B, Z](fa: F[A], fb: Eval[F[B]])(f: (A, B) => Z): Eval[F[Z]] =
fb.map(fb => map2(fa, fb)(f))
....
}
2項演算子に関しては、map2
を使うことでアプリカティブ・スタイルを隠蔽することができる。
同じものを 2通りの方法で書いて比較してみる:
(3.some, List(4).some) mapN { _ :: _ }
// res20: Option[List[Int]] = Some(value = List(3, 4))
Apply[Option].map2(3.some, List(4).some) { _ :: _ }
// res21: Option[List[Int]] = Some(value = List(3, 4))
同じ結果となった。
Apply[F].ap
の 2パラメータ版は Apply[F].ap2
と呼ばれる:
Apply[Option].ap2({{ (_: Int) :: (_: List[Int]) }.some })(3.some, List(4).some)
// res22: Option[List[Int]] = Some(value = List(3, 4))
map2
の特殊形で tuple2
というものもあって、このように使う:
Apply[Option].tuple2(1.some, 2.some)
// res23: Option[(Int, Int)] = Some(value = (1, 2))
Apply[Option].tuple2(1.some, none[Int])
// res24: Option[(Int, Int)] = None
2つ以上のパラメータを受け取る関数があったときはどうなるんだろうかと気になっている人は、
Apply[F[_]]
が ApplyArityFunctions[F]
を拡張することに気付いただろうか。
これは ap3
、map3
、tuple3
… から始まって
ap22
、map22
、tuple22
まで自動生成されたコードだ。
Apply には合成則という法則のみが1つある:
trait ApplyLaws[F[_]] extends FunctorLaws[F] {
implicit override def F: Apply[F]
def applyComposition[A, B, C](fa: F[A], fab: F[A => B], fbc: F[B => C]): IsEq[F[C]] = {
val compose: (B => C) => (A => B) => (A => C) = _.compose
fa.ap(fab).ap(fbc) <-> fa.ap(fab.ap(fbc.map(compose)))
}
}