この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
cats.Applicativeの使い道がいまいちわからないのですごいH本を参考に考えてみました。
Applicative
まずcatsでのApplicativeの定義は下記です(抜粋)。
trait Applicative[F[_]] extends Functor[F] {
def ap[A, B](ff: F[A => B])(fa: F[A]): F[B]
def pure[A](a: A): F[A]
def map[A, B](fa: F[A])(f: A => B): F[B] = ap(pure(f))(fa)
}
ap
についてすごいH本では下記のような例が紹介されています。<*>
によってApplicativeを扱っていない関数をApplicative値に適用できます(<*>
がcatsでいうap
)
Prelude Control.Applicative> (pure (+)) <*> Just 1 <*> Just 2
Just 3
catsでも同様に<*>
はap
のエイリアスとして定義されていて、同等の使い方ができます。
@ import cats.Applicative
import cats.Applicative
@ Applicative[Option].pure { a:Int => b:Int => a+b } <*> 1.some <*> 2.some
res6: Option[Int] = Some(3)
さて、HaskellのApplicativeは以下の演算子<$>
をエクスポートしています。
(<$>) :: (Functor f) => (a -> b) -> f a > f b
f <$> x = fmap f x
これを使うと先ほどの例は以下のようになります。
Prelude Control.Applicative> (+) <$> Just 1 <*> Just 2
Just 3
部分適用することもできます。
Prelude Control.Applicative> :t ((+) <$> Just 1)
((+) <$> Just 1) :: Num a => Maybe (a -> a)
catsではmapN
がこれに相当するように見えます。これを使うと<$>
のように部分適用もできます。しかし実際のところmapN
はSemigroupal#mapN(n = {2....N})
のエイリアスで、型も異なっています。
@ import cats.implicits._
import cats.implicits._
//mapN
@ (1.some, 2.some).mapN(_+_)
res1: Option[Int] = Some(3)
//map2
@ Applicative[Option].map2(1.some, 2.some)(_+_)
res5: Option[Int] = Some(3)
//部分適用
@ (1.some, _:Option[Int]).mapN(_+_)
res17: Option[Int] => Option[Int] = ammonite.$sess.cmd17$$$Lambda$1873/1950478035@7432cb37
@ (Applicative[Option].map2(1.some, _:Option[Int])(_+_))
res19: Option[Int] => Option[Int] = ammonite.$sess.cmd19$$$Lambda$1896/649325384@77f21c0a
そこでmap2
、map3
の定義を見るとSemigroupal#product
が使われているのがわかります。
def map2[F[_], A0, A1, Z](f0:F[A0], f1:F[A1])(f: (A0, A1) => Z)(implicit semigroupal: Semigroupal[F], functor: Functor[F]): F[Z] =
functor.map(semigroupal.product(f0, f1)) { case (a0, a1) => f(a0, a1) }
def map3[F[_], A0, A1, A2, Z](f0:F[A0], f1:F[A1], f2:F[A2])(f: (A0, A1, A2) => Z)(implicit semigroupal: Semigroupal[F], functor: Functor[F]): F[Z] =
functor.map(semigroupal.product(f0, semigroupal.product(f1, f2))) { case (a0, (a1, a2)) => f(a0, a1, a2) }
<$>
とmapN
は似ていますがやはり別物に見えます。
ここでproduct
はSemigroupal
において以下のように定義されています。
@typeclass trait Semigroupal[F[_]] {
def product[A, B](fa: F[A], fb: F[B]): F[(A, B)]
}
product
をfmap
とap
で実装してみます。
implicit def semigroupal[F[_] : Applicative]: Semigroupal[F] = new Semigroupal[F] {
override def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] = Functor[F].fmap(fa) { a: A => b: B => (a, b) } <*> fb
}
これは タプルを扱っているものの<$>
と<*>
を組み合わせて使ったときと同じです。
まとめ
Applicativeを使うことでApplicativeを扱わない関数をApplicative値に適用できることがわかりました。