最近 Functional Programming in Scala の勉強会をずっとしているのですが,ようやく12章の Applicative Functor に入りました.ところが,急に登場した Applicative という概念がいまいち勉強会の時点ではつかめておらず,少し頭の中を整理したいと思ったので簡単にまとめてみようと思います.あまり体系的にまとめるつもりはありません.自身の理解のメモとしてインターネットに放流しておく予定です.
(Applicative Functor はアプリカティブファンクタと以下書きます.日本語ググラビリティのためにそうします.他のこれ関連の用語も同様にカタカナで表記します.)
なお,モノイドやモナドは登場させません.これには意図があって,『すごい Haskell たのしく学ぼう!』という本では,モナドの説明なしにアプリカティブファンクタの説明をじっくり行っていたためです.この説明はわかりやすいなと思いました.今回はこの本の11章を参照しながらいろいろと考えていきます.
勉強会で使用している本はこちら.
Scala関数型デザイン&プログラミング ―Scalazコントリビューターによる関数型徹底ガイド (impress top gear)
- 作者: Paul Chiusano,Rúnar Bjarnason,株式会社クイープ
- 出版社/メーカー: インプレス
- 発売日: 2015/03/20
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (7件) を見る
すごいHaskellたのしく学ぼう.これは関数型プログラミングの理解の助けになる本だなと思います.
- 作者: Miran Lipovača,田中英行,村主崇行
- 出版社/メーカー: オーム社
- 発売日: 2012/05/23
- メディア: 単行本(ソフトカバー)
- 購入: 25人 クリック: 580回
- この商品を含むブログ (73件) を見る
コードそのものは Scala で書きます.すごい Haskell 本のソースコードを読みつつ,Scala で書くとこうなるかなと翻訳して書いています.ただ厳密にはちょっと違いそう.
ファンクタとは
ファンクタとは「関数で写せるもの」のことを言います.要するに写像 (map) です.といったところで,???が頭に浮かぶかもしれません.さっそくですが実装を見てみましょう.
trait Functor[F[_]] { def fmap[A, B](fa: F[A], f: A => B): F[B] }
これを見ると一目瞭然なのですが,F
の中身の型を A
から B
に変えているものですね.F
にはたとえば Option
や List
といった型が入ります.この外側の型を保ちながら,内側の型を別の型に変換することをしてくれる抽象的な概念をファンクタと呼びます.難しくいうと,値があるかないかという Option
の文脈を保ちながら,型を変換しているということです.
ファンクタは1引数しか受け取ることができません.なので,Either
のような2つの型変数をもつ型をファンクタにするには,一度部分適用をして,あと1つ型変数を引数に取るだけの状態にする必要があります.
さて,ファンクタのざっくりとした説明はここまでです.11章では lift などの概念も実は登場してきていますが,今回は省きました.
アプリカティブファンクタとは
さて,欲しくなる場面ですが,たとえば次のようなコードがあったとしてみてください.
val f = n => n * 3 val someF = Some(f) val some5 = Some(5) // いい感じに Some(f(5)) をしたい
Some(f)
の f に Some(5)
の5を適用したいとなった場合にどうしたらよいのでしょうか.普通のファンクタを扱う限り,これはちょっと難しそうです.というのも,普通のファンクタでできるのは,「通常の関数で」「ファンクタの中の値を」写すことだけだからです.上述したように,一度 Some
の中の値を両方とも取り出して,再度 Some に詰め直すという作業をするか,あるいは fmap
を実装しているのであればそれを2回適用すればよさそうに見えます.
ただ,もう少しスマートにやる方法があります.それがアプリカティブファンクタという概念です.コードを見てみましょう.
trait Applicative[F[_]] extends Functor[F] { def pure[A](a: => A): F[A] def <*>[A, B](fa: => F[A], f: F[A => B]): F[B] }
要するに,F
の中に入った関数を元の F
に適用 (apply) して,その結果値を受け取ることができるものです.それゆえに applicative …?ただ,こうすることで,ファンクタでは1引数関数でしか処理できなかったものを,アプリカティブファンクタでは2引数関数で処理できるようになります.これはファンクタよりも強力になったことを意味します.
pure
というのは,なんでもない a という値を受け取ると,それを F
の中に入れて返すものです.<*>
は,見たとおりで F
という型の中で関数を適用してくれるスグレモノです.
ところで,Haskell の Applicative にはさらに便利な関数があるようです.それが,liftA2
という関数です.liftA2
は,「通常の2引数関数を,2つのアプリカティブ値を引数に取る関数に昇格させる」という機能を持っています.実装を見てみましょう.
trait Applicative[F[_]] extends Functor[F] { def pure[A](a: => A): F[A] def <*>[A, B](fa: => F[A], f: F[A => B]): F[B] def liftA2[A, B, C](a: F[A], b: F[B], f: (A, B) => C): F[C] // ←追加された }
実装のとおりです.ここまでそろうと,ようやくアプリカティブファンクタを実装することができます.実装してみました.
trait Applicative[F[_]] extends Functor[F] { def pure[A](a: => A): F[A] override def fmap[A, B](fa: F[A], f: A => B): F[B] = this(fa, pure(f)) def <*>[A, B](fa: => F[A], f: F[A => B]): F[B] = liftA2(f, fa, (_: A => B)(_: A)) private def apply[A, B](fa: => F[A], f: F[A => B]): F[B] = <*>(fa, f) def liftA2[A, B, C](a: F[A], b: F[B], f: (A, B) => C): F[C] = this(b, fmap(a, f.curried)) }
コンパイルも通ったしたぶん大丈夫…!private def
で apply
を追加したのは,単純に <*>
を使おうとするとうまいこと中置記法できなくてかっこ悪いと思っただけです.あんまり深い意図はありません.
ところで <*>
と fmap
と liftA2
が循環参照しているので,最終的にこの Applicative を使う側でどこかを実装してやる必要があるのでしょうか.Functional Programming in Scala の中でも循環参照している実装例が出てきたのですが,使う側で個別実装したら大丈夫だったので,たぶんそういうことなのだと思っています.
ということで,少し実装をしてみましょう.たとえば Maybe
という型があったとします (Haskell から拝借).
sealed trait Maybe[+A] { def <*>[B](f: Maybe[A => B])(implicit F: Applicative[Maybe]): Maybe[B] = F <*> (this, f) } object Maybe { case class Just[+A](a: A) extends Maybe[A] case object Nothing extends Maybe[Nothing] } object applicatives { implicit def maybeApplicative: Applicative[Maybe] = new Applicative[Maybe] { override def pure[A](a: => A): Maybe[A] = Just(a) override def fmap[A, B](fa: Maybe[A], f: A => B): Maybe[B] = fa match { case Just(a) => pure(f(a)) case Nothing => Nothing } override def <*>[A, B](fa: => Maybe[A], f: Maybe[A => B]): Maybe[B] = f match { case Just(something) => fmap(fa, something) case Nothing => Nothing } } }
これであっているのかは若干怪しいところですが (ちょっと美しくない実装な気もする),おおよそのイメージは掴んでもらえるはずです.実行して試してみましょう.
object Main extends App { val just5 = Just(5) val justF = Just((n: Int) => n * 3) implicit val maybeApplicative = implicitly[Applicative[Maybe]](applicatives.maybeApplicative) println(just5 <*> justF) }
これを実行すると,結果は
> Just(15)
と返ってきます.やりたかったことが実現できましたね.
まとめ
- ファンクタは
map
のことであり,関数の写しである.F
の中身を単に取り出して変換する. - アプリカティブファンクタは Haskell だと
<*>
であり,2つのF
を受け取って,両者を引数に受け取る関数を適用して結果値を取り出したり,あるいは,F
の中に入った関数をF
の中で適用して結果値を受け取ることができる.- まとまながらなるほどなと思ったんですけど,これがゆえに Functional Programming in Scala では
Traverse[F[_]]
の説明に入ってゆくのですね.
- まとまながらなるほどなと思ったんですけど,これがゆえに Functional Programming in Scala では
しかし,とくにアプリカティブファンクタの方がまだまだ理解が100%になった気はしませんね.つかめるまでもう少しエクササイズをしたいと思います.