第5章は関数に関してのもう少し高度なトピックを扱っている模様。
カリー化
有名なやつ。カリー化とは、複数の引数を取る関数に一つだけ引数を与えて呼び出した時、残りの引数を取る関数を返すようにすることである。例えば二引数を取るmax関数に一つだけ引数を与えた場合、呼び出し失敗とする言語は多いと思うが、Haskellではそうならない。もう一つ引数を取る関数を返すだけである。
max 4 5 # => 5 f = max 4 f 5 # => 5
このように、要求される引数より少ない数の引数を渡すことを部分適用という。
セクション
中置関数でも部分適用することができる。書き方としては、以下のように中置関数の片側にのみ値を置いて括弧でくくる。
f = (/10) f 20 # => 2 f = (10/) f 2 # => 5
高階関数
関数を引数に取る関数を高階関数という。
applyTwice :: (a->a) -> a -> a applyTwice f x = f (f x) applyTwice (+3) 10 # => 16
map, filter
map, filterの使い方が紹介されている。RubyでもJavaScriptでも、関数型パラダイムを推していない言語にもそのままの名前で広く取り入れられている構文なのでもはや説明不要か。
ghci> map (*2) [1,2,3] [2,4,6]
ghci> filter (>1) [1,2,3] [2,3]
リストに対する変換、フィルタリングはリスト内包表記でもできるので好みで選ぶと良いらしい。
ラムダ式
無名関数を作るための記法
ghci> (\x -> x+2) 3 5
畳み込み
よくあるfold系の関数のこと。
ghci> :t foldl foldl :: Foldable t => (b -> a -> b) -> b -> t a -> b
二引数関数、アキュムレータ(初期値)、リストを引数に取り、アキュムレータとリストの要素に対して二引数関数を適用→その値をアキュムレータとしてその次の要素とアキュムレータに対して二引数関数を適用...をリストの終わりまで繰り返していく。
左畳み込みと右畳み込み
左畳み込みと右畳み込みは、その名の通り左から処理するか右から処理するかの違い。どちらを選んでも変わらない場合もある。例えばsumを実装するときはどちらから処理しようが変わりはない。
ghci> sumFoldR xs = foldr (\acc x -> acc+x) 0 xs ghci> sumFoldl xs = foldl (\acc x -> acc + x) 0 xs
しかし、例えばmap関数のようにリストから新しいリストを構築するときは右畳み込みを使うことのほうが多い。
map' f xs = foldl (\acc x -> acc ++ [f x]) [] xs map'' f xs = foldr (\x acc -> f x : acc) [] xs
foldlの何が辛いかというと、++でリストを結合しなければならない点。foldrでは右から処理を行う関係上、リストの先頭に要素を追加していくような処理になるが、foldlでは左から処理を行うために、リストの末尾に要素を足していくことになる。参考までにリストの結合演算子の型を置いておく。
ghci> :t (:) (:) :: a -> [a] -> [a] ghci> :t (++) (++) :: [a] -> [a] -> [a]
見ての通り、(:) 演算子は先頭要素への追加にしか使えない。++ はこの演算子より遥かに動作が遅いとのこと。
foldl1, foldr1
foldl1, foldr1という関数があり、これはアキュムレータを渡す必要がない。リストの最初の要素を初期値として使用してくれるらしい。リストの最大値を取るような処理だったらこれで良さそう。リストが空の場合はエラーを吐く。
ghci> maximum' = foldl1 max ghci> maximum' [1,2,3,4,5] 5
$を使った関数適用
f $ x = f x
のように、関数とその引数をとって適用するだけの関数適用演算子がある。括弧を減らして可読性を上げるのに役立つ。
ghci> sum $ take 3 $ repeat 1 3
関数合成
.
演算子を使うと複数の関数を合成できる。 ただし、関数定義から分かるように、一引数関数にしか使えない。
ghci> :t (.) (.) :: (b -> c) -> (a -> b) -> a -> c ghci> map (negate . abs) [1,2,3,4,5] [-1,-2,-3,-4,-5]