
HaskellとcatsのApplicative Functorについて考えてみた
この記事は公開されてから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値に適用できることがわかりました。







