Posted at

すごいH本読んだからまとめ 7「高階関数」


はじめに


高階関数


そもそも高階関数とは



  • 引数戻り値に関数を渡せる関数


  • 高級関数ではなく高階関数

-- (a -> a)の関数を2回実行する関数

-- 1つめの引数に関数を与えたいので型の宣言を()でくくって優先順位をコントロール
applyTwice :: (a -> a) -> a -> a
applyTwice f x = f (f x)

-- 'a': ['b'] は "ab"
*Main> applyTwice ('a':) ['b']
"aab"

-- 関数の適用順を変更するflip関数を定義してみる。
flip' :: (a -> b -> c) -> b -> a -> c
flip' f y x = f x y

-- take関数を逆転してみる
-- take n [a] で「前からn個だけリストからアイテムを取得する」関数
flippedTake = flip' take
-- 先にリストを部分適用できる。
*Main> taken = flippedTake [1,2,3,4,5]
-- ほしい数をあとから取得する。
*Main> taken 3
[1,2,3]


  • 関数を戻り値として扱う方法はカリー化を勉強したときに確認済み


Haskellに定義されている便利な高階関数たち


map

関数とリストを受け取って、リストのアイテムそれぞれに関数が適用されたリストを返す

*Main> :t map

map :: (a -> b) -> [a] -> [b]
*Main> map (+3) [1,2,3]
[4,5,6]
*Main> map (+3) (map (+3) [1,2,3])
[7,8,9]
-- 関数のリストも返せる
*Main> pluslist123 = map (+) [1,2,3] -- [((+) 1), ((+) 2), ((+) 3)]のリスト
*Main> (head pluslist123) 5 -- ((+) 1) 5
6


filter

関数とリストを受け取って、受け取った関数がTrueを返す値のみを取得したリストを返す。


*Main> :t filter
filter :: (a -> Bool) -> [a] -> [a]
*Main> filter ((==) 3) [1,2,3,1,2,3]
[3,3]


foldl(r)


  • foldlをリストに適用する場合、下記の通り関数が実行される


    1. リストの先頭から値を1つ取ってきて関数を適用する

    2. 適用した結果は次の計算に使われる

    3. 最後にすべてのリストを走査した結果を返す



  • 上記のような走査を畳み込みという。

  • 畳み込みの初期値をアキュミュレータという

  • foldlは左から畳み込む、foldrは右から畳み込む

  • 他のプログラミングでreduceって名前で定義されてることもある

foldl

*Main> :t foldl

foldl :: Foldable t => (b -> a -> b) -> b -> t a -> b
-- (Foldable t)は型クラス 直訳すると「畳み込める」
-- []で定義されるリスト以外にも畳み込める「型t」ならfoldlを適用して良いということ

-- この場合0がアキュミュレータ
*Main> foldl (+) 0 [1,2,3]
6

-- 真偽値と数を受け取って受け取った真偽値と奇数かどうかで演算を行う関数
isOddAnd :: (Integral a) => Bool -> a -> Bool
isOddAnd bool x = (x `mod` 2) /= 0 && bool

-- foldl関数を適用してすべての値が奇数かチェックできる
*Main> foldl isOddAnd True [1,3,5,7,9]
True
*Main> foldl isOddAnd True [1,3,5,7,10]
False

-- 部分適用して新しい関数を作る
*Main> isAnyOdd = foldl isOddAnd True
*Main> isAnyOdd [1,3,5,7,9]
True
*Main> isAnyOdd [1,3,5,7,10]
False

foldr


*Main> :t foldr -- foldrは引数の順番が変わる
foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b

-- (:) を使って空のリストにリストの値を代入できる。
*Main> foldr (:) [] [1,2,3,4,5]
[1,2,3,4,5]

-- 偶数なら2倍したリストを返す関数
evenDoubleAppend :: (Integral a) => a -> [a] -> [a]
evenDoubleAppend x
| x `mod` 2 == 0 = (2 * x :)
| otherwise = (x :)

*Main> foldr evenDoubleAppend [] [1,2,3,4,5]
[1,4,3,8,5]


foldl(r)の派生


  • 畳み込みの途中経過をすべて返すscanl(r)関数

  • 畳込みの初期値(アキュミュレータ)を操作するものの先頭にするfoldl1, foldr1関数

*Main> :t scanl

scanl :: (b -> a -> b) -> b -> [a] -> [b]
*Main> scanl (+) 0 [1,2,3]
[0,1,3,6]
*Main> :t scanr
scanr :: (a -> b -> b) -> b -> [a] -> [b]
*Main> scanr (+) 0 [1,2,3]
[6,5,3,0]
*Main> :t foldl1
foldl1 :: Foldable t => (a -> a -> a) -> t a -> a
*Main> foldl1 (+) [1,2,3]
6
*Main> :t foldr1
foldr1 :: Foldable t => (a -> a -> a) -> t a -> a
*Main> foldr1 (+) [1,2,3]
6