Functorとしての関数とFunctorとしてのリスト

最近すごいH本を読み直していて、11章のFunctorとしての関数の話で気付きがあって、一度忘れてまた思い出したので今度はちゃんと書き留めておく。大した話ではない。

Haskellのリストは、そういう需要が分かりやすく存在するからなのか分かりやすいFunctorで、

fmap (*2) [1,2,3]

と書いたらこれはちゃんと

[2,4,6]

となってくれる。リストの各々に大して関数を適用したいという場面はある程度プログラミングをしていれば無限に遭遇する場面だと思う。

ただ、関数もFunctorであって、

fmap (+1) (*2)

(*2).(+1)

と同じになるというのは若干分かりづらい。関数がFunctorであるというのをもうちょっと調べると、型aにFunctor*1(b->a)とできるということになる。ちょっと実験してみると、

(return :: (a -> (b -> a))) 1 2

は1になるし、

(return :: (a -> (b -> a))) 1 3

も1になるし、

(return :: (a -> (b -> a))) 1 4

も1になる。returnってそういうものだった。

さて、型aを包んで(b -> a)にしてくれる関数のFunctor(b -> )が分かりづらいという話だった。しかし一方、型aを包んで[a]にしてくれるリストはFunctorとしてわかりやすい。しかし、よくよく考えてみると、高校でひょっとしたら学ぶようにリスト[a]、あるいはa型の数列は結局のところ自然数から型aへの写像であった。例えば、初項1、公差2の数列

1,3,5,7,...

は、関数

(\n -> 2*n - 1)

と同一視できる。数列のn番目を求めたければ、この関数にnを入れれば確かにうまく行く。
そう考えると、型aを包んで[a]にするというのは、型aを包んで(Int -> a)にするということになる。例えば、fmapを使って数列を1増やすというのを考える。

fmap (+1) [1,3,5,7..]

は、

[2,4,6,8..]

になる。一方、

fmap (+1) (\n -> 2*n -1)

を考えると、ここに1,2,3,...を入れてできた数列はさっきの

[2,4,6,8..]

に一致する。

まとめると、Functor(b ->)が型aを包んで(b -> a)にするのは、bをIntと見ると、Functor[]が型aを包んで[a]にするのとちゃんと似ているなあというお話でした。

*1:->) b)、あるいは(b ->)で包んで((ウルトラどうでもいいが「くるむ」と読みたい