11章:ファンクターからアプリカティブファンクターへ
Functor
Functorは型クラスであり、前述の通り型クラスはインターフェースのような物。
Functorは全体を写せる(map over)な性質を持つ型クラスを指している。
またインターフェースの観点から見ると、あるクラスがFunctorであるという事はつまりFunctorが実装すべきメソッドを実装しているということになる。
Functorの定義は
class Functor (f :: * -> *) where
fmap :: (a -> b) -> f a -> f b
となっており、Functorである為に実装すべきメソッドはfmap
のみと分かる。
fmapメソッドは「aを引数に取りbを返す関数」と「ファンクターなa」を引数にとって「ファンクターなb」を返す関数である。
実際には「fmapの実装がファンクター則を満たしている」という事ががファンクターとして必要な事になっている。
ファンクター則
ファンクター則は2つの法則があり、Functorであるにはこのどちらも満たしている必要がある。
第一法則
恒等関数の保存とも言われ、fmap id (f a) = id (f a)
である事。
これは「引数をそのまま返すidという関数」がある時、「fmapでidをaに適用した値」と「idをaに適用した値」が同じであるという事。
第二法則
合成関数の分配とも言われ、fmap (g . h) (f a) = fmap g (fmap h (f a))
である事。
これはgとhの関数がある時、「fmapでgとhの合成関数をaに適用した値」と「「fmapでhをaに適用した値」に更にfmapでgを適用させた値」が同じであるという事。
結局分かりづらいがfmapを使って関数を順番に適用した物と合成関数を適用した物が同じであるという事をいっている。
あるクラスがFunctorであると、そのクラスであることを意識せずに中身のaに対しての操作を画一的に行えるのがうれしい(はず)。
アプリカティブファンクター
Functorの強化版であるApplicativeFunctorは
class Functor f => Applicative (f :: * -> *) where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
という定義を持っている。
ApplicativeFunctorはFunctorと同じく型クラスであり、ApplicativeFunctorであるクラスは同時にFunctorとしても扱えることになる。
実装すべきメソッドとしてpure
と<*>
の2つメソッドを持っている。
pureは「値a」を「アプリカティブな値a」に変換する関数であり、
<*>は「アプリカティブな値としてaをbに変換する関数が入った物」と「アプリカティブな値a」を引数として取り、「アプリカティブな値b」を返す関数である。
値として関数が入ったFunctor f (a -> b)
を考える。
このファンクターな「a -> b
」関数を「ファンクターなa」に対してfmapを使って適用しようとしても、fmapで適用する関数は「a -> b
」であって、「ファンクターなa -> b
」では無いので、一度中身の関数を取り出さなければならない。
しかしFunctorはfmapつまり「ファンクターな値を画一的に操作する方法」しか保証しないので、クラスによってはFunctorから値を取り出す方法を提供していないかもしれない。
そこでアプリカティブファンクターで、<*>が定義されていると関数がファンクターに包まれている状態でもファンクターな値に対してその値を適用できるようになる。
Functorでは操作対象の値のみがファンクターであったのに対して、ApplicativeFunctorでは操作方法の関数がファンクターであっても操作ができるようになっている。
アプリカティブ則
アプリカティブにもファンクターと同じく、満たすべき法則がある。
pure id <*> v = v
pure (.) <*> u <*> v <*> w = u <*> (v <*> w)
pure f <*> pure x = pure (f x)
u <*> pure y = ($ y) <*> u
説明は省略されてる。