Applicative Styleで試してみる
(->)
アプリカティヴに定義された<*>
の中身は
(<*>) f g = \x -> f x (g x)
だということを意識しながら、典型的なApplicative Styleの場合:
f <$> g <*> h
がどんな函数を産みだすのかを(文系情弱なので)愚直に一歩一歩確認してみる。まず(<$>) f g
は忠実に (<*>) (pure f) g
として、\x -> pure f x (g x)
になるが、(pure f)
は引数を無視してfを返す函数((->)
アプリカティヴのpure
はf
の実質を変えずに引数の項数だけを形式的に増やす作用だ)なので、\x -> f (g x)
になる。これはもちろん(<$>)
が(.)
と等価なことと平仄が合っていてf.g
にほかならないが、このラムダ形式のまま次に進む:
f <$> g <*> h =
(\x -> f (g x)) <*> h
で、次に<*>
を適用してやると:
f <$> g <*> h =
(\x -> f (g x)) <*> h =
\y -> (\x -> f ( g x )) y (h y)
ここで(\x -> f (g x)) y
の部分はまさにyに対するラムダ式の無名函数の適用なのでそうしてやれば:
f <$> g <*> h =
(\x -> f (g x)) <*> h =
\y -> (\x -> f ( g x )) y (h y) =
\y -> f (g y) (h y)
おお。これはわかりやすい! これはつまり:
f <$> g <*> h $ x =
f (g x) (h x)
ということだ。引数に g を作用させたものと 引数に h を作用させたものとを f に引き渡すような函数が構成された、というわけなんだな。hがなければ、引数にgを作用させたものをfに引き渡すだけだから、函数合成 f.g になるのはもちろんだ。だからこれは函数合成を一般化した形式を提供していることになる。もちろん f はApplicativeな適用先(g ,h, …)と少なくとも同じだけの項数を持った函数である必要がある。
f <$> g <*> h <*> i $ x =
f (g x) (h x) (i x)
で、たとえばf = \ x y z -> [x, y, z]
とかにすると、この函数は引数x
を取って[g x, h x, i x]
を返してくれるものになるわけだ。つまり、ある同じ引数を同時に多数の函数に引き渡して、そのそれぞれの結果を加工してまとめ上げる、という用途に使えるんだな(おお、なにやらモナドの気配がしてきたような……)。
しかし、そう考えると(->)
アプリカティヴ・ファンクターの<*>
の定義とpureの定義は実に巧妙に噛み合ってることになる。凄い。