関数プログラミングを学ぼうと思って数週間。関数合成の考え方、捉え方でいつもハマる。もっと周辺知識を蓄えるまで後回しにしようとも思ったが、現時点の自分の理解として整理することにした。
とにかくややこしい
まず、おさらいとして、これは、わかる。
f :: b -> c
g :: a -> b
f . g :: a -> c
なぜなら、このように
(.) :: (b -> c) -> (a -> b) -> a -> c
(.)
演算子が「 1引数渡すと、g
の値を経由してf
の値を返す関数」を返すことが単純にわかるから。
では、この場合は?
f :: a -> b
g :: c -> d -> f
f . g :: ???
うーん。やはり整理すらできない。幾つかのサイト・書籍を参考にさせていただき、考察する。
ヒントはカリー化による部分適用にあるようだ。g
は、部分適用により 1引数渡すと、1引数関数を返す関数 であるとも言える。そのため、f.g
といった合成をした場合、 1引数渡すと、g
の返り値(1引数関数)を経由して、f
の値を返す関数となる。つまり、f
の1引数が関数になる事を強制していて、これを満たさない場合にはコンパイルできない。
よって、このような実装は実現しない。
let f = \x -> x + 2
let g = \x y -> x * y -- \x -> (\y -> x * y) カリー化されてこのように扱われる
(f . g) 3 4 -- 本当は14になってほしいが...
<interactive>:283:1:
No instance for (Num (a0 -> a0)) arising from a use of ‘it’
In a stmt of an interactive GHCi command: print it
思った通りに合成したいなら g
の返り値が値となるような工夫が必要となる。
例えば、
g 3 :: Num a => a -> a
(f . (g 3)) 4
このようにすれば、14
が求まる。ただし、この式はf.g
や g
が1引数関数であることを強調しすぎていて、本質的な意味では分かりにくい。
一方、次のようにすることで、引数(値) を渡すと、g
の値(1引数関数)を経由して、1引数関数を返す関数を合成する こともできる。
((f.) . g) 3 4 -- 14が求まる
これは、(.)
演算子にf
を部分適用すると、(f.) :: Num c => (a -> c) -> a -> c
となる性質を利用したものである。これを踏まえて上の例をもう一度見てみると、カリー化により((f.) . g) 3
が1引数関数を返すので、その関数に4
を渡すことでようやく値としての14
が求まる。
参考サイト・書籍
###サイト
Haskell の関数合成でひっかかってたこと
Haskellの関数合成