すごいHaskell五章(前半)
高階関数
引数として関数を受け取ったり、返り値として関数を返す関数。
カリー化
グワーッ!キモン!
複数の引数を取る関数は、1つ引数を受け取るたびに、引数が一つバインディングされた新しい関数を返す。
ghci> :t max max :: Ord a => a -> a -> a ghci> :t max 1 max 1 :: (Ord a, Num a) => a -> a ghci> :t max 1 3 max 1 3 :: (Ord a, Num a) => a
引数を全て適用せずに、一部分だけ適用することを部分適用と呼ぶ。
max関数は次のように読み替えられる。
max :: Ord a => -> a -> a -> a max :: Ord a => -> a -> (a -> a)
こう見ると、a型の値を引数に取り、「a型の値を引数に取り、a型の値を返す関数」を返す関数、と読めるとのこと。
部分適用で関数を作ると、脳からなんかでてくる気持ちよさがある。
セクション
中置関数もカッコで囲むことで部分適用できる。
ただし、引き算を使いたい時は、subtract関数を使う。(負の数との区別のため)
ghci> let addOne = (+ 1) ghci> addOne 1 2 ghci> let modByTen = (`mod` 10) ghci> mod mod modByTen ghci> modByTen 100 0 ghci> modByTen 9 9
関数を引数に取る関数
applyTwiceにapplyTwiceを渡した時の挙動が理解できてない。、
ghci> applyTwice (* 5) 5 125 ghci> applyTwice applyTwice((* 5)) 5 3125
applyTwice ((* 5) (* 5)) a
(((* 5) (* 5)) ((* 5) (* 5))) a
((((a * 5) * 5) * 5) * 5)
ってことなのかな。
ghci> applyTwice (3:) [1] [3,3,1] ghci> applyTwice (applyTwice (3:)) [1] [3,3,3,3,1]
こっちのほうがわかりやすい。
applyTwice ((3:) ((3 :))) a
(3: (3 :(3: (3 :)))) a
(3: (3 :(3: (3 :)))) [1]
(3: (3 :(3: (3 : [1]))))
中に入るわけじゃないのかな……
リストのリストへのzipWith
ghci> zipWith (zipWith (*)) [[1,2,3], [2,3,4], [3,4,5]] [[4,5,6], [5,7,8]] [[4,10,18],[10,18,28],[18,28,40]]
挙動がよく分からなかったんだけど、
zipWith (zipWith func) [[a, b], [c, d]] [[e, f], [g, h]] [zipWith func [a, b] [e, f], zipWith func [c, d] [g, h]] [[func a e, func b f], [func c g, func d h]]
って感じか。
flip
「引数を2つ取る関数」を引数にとって、「2つの引数を入れ替えた関数」を返す。んだけどよく分からない。
flip' :: (a -> b -> c) -> (b -> a -> c) flip' f = g where g x y = f y x
ここで肝心ところは、 g x y = f y xなんだろうけど、これは引数の関数の中で、受け取る引数が(型ごと)入れ替えられて、新しい関数を作って返すってことなのかな。
flip' :: (a -> b -> c) -> b -> a -> c flip' f y x = f x y
これってともすれば、単にflipが受け取った引数を入れ替えて、与えてるだけに見えるんだよな。
カリー化を考えても、引数が後で入れ替えられるだけに見える。
ghci> zip [1,2,3,4,5] "Hello" [(1,'H'),(2,'e'),(3,'l'),(4,'l'),(5,'o')] ghci> flip zip [1,2,3,4,5] "Hello" [('H',1),('e',2),('l',3),('l',4),('o',5)]
これって
ghci> zip "Hello" [1,2,3,4,5] [('H',1),('e',2),('l',3),('l',4),('o',5)]
と同じに見えるんだが。
ghci> zipWith subtract [2,2..] [10,8,6,4,2] [8,6,4,2,0] ghci> zipWith (flip subtract) [2,2..] [10,8,6,4,2] [-8,-6,-4,-2,0]
部分適用していろいろ当てはめたら効果がわかってくるのかな。
mapとfilter
リストと関数を受け取ってリストの全要素に対して関数を適用する。内包表記でも書き替えられる。
ghci> map (+1) [1,2,3,4] [2,3,4,5] ghci> [x+1 | x <- [1,2,3,4]] [2,3,4,5]
ghci> filter even [1,2,3,4,5] [2,4] ghci> [x | x <- [1,2,3,4], even x] [2,4]
mapとfilterの使い方
解があると分かっている範囲から解になり得る集合をフィルタする
このへんになってくると頭がこんがらがってくる。
例として載ってる、10万以下の数のうち3829で割り切れる最大の数を求める問題は、リストを降順で作ってるのもポイントっぽい。最大の数だから、条件の中で大きい方から探せばいい。
takeWhileとかいう条件に一致している限りリストの要素を取り出す関数。『プログラミングHaskell』でいつかやったはずなんだけどすっかり忘れてる。
1000より小さい全ての2の倍数の立方数の積を求めることにする。
ghci> product (takeWhile (< 10000) (let f n = (n `mod` 3) == 0 in filter f (map (^3) [1..]))) 1339176927923476992000
こういう関数同士を組み合わせた長い式は、右のほうから書いていくといいみたい。この式だと下のように分解していける。
ghci> product (takeWhile (< 10000) (let f n = (n `mod` 3) == 0 in filter f (map (^3) [1..]))) 1339176927923476992000 ghci> takeWhile (< 10000) (let f n = (n `mod` 3) == 0 in filter f( map (^3) [1. .])) [27,216,729,1728,3375,5832,9261] ghci> let f n = (n `mod` 3) == 0 in filter f( map (^3) [1..]) [27,216,729,(無限リストなので省略) ghci> map (^3) [1..] [1,8,27,(無限リストなので省略) ghci> [1..] [1,2,3,(無限リストなので省略)
filterに渡す関数がセクションだけだとうまく書けなかったのでlet使った。まだ出てきてないけど、多分ラムダ式でも書けるはず。
リスト内包で書くと下のようになる。
ghci> product (takeWhile (< 10000) [m | m <- [n^3 | n <- [1..]], (m `mod` 3) == 0]) 1339176927923476992000
この場合もリスト内包の内側から書いていくといいみたい。
ghci> product (takeWhile (< 10000) [m | m <- [n^3 | n <- [1..]], (m `mod` 3) ==0]) 1339176927923476992000 ghci> takeWhile (< 10000) [m | m <- [n^3 | n <- [1..]], (m `mod` 3) == 0] [27,216,729,1728,3375,5832,9261] ghci> [m | m <- [n^3 | n <- [1..]], (m `mod` 3) == 0] [27,216,729,(無限リストなので省略) ghci> [n^3 | n <- [1..]] [1,8,27,(無限リストなので省略) ghci> [1..] [1,2,3,(無限リストなので省略)
本に載ってるコラッツ数列のchain関数はガードが網羅的じゃないから警告が出るのと、0を入れると止まらなくなるっぽい。
ガードはotherwiseを付け足してerrorを返すようにした。0を入れるのはもうしょうがない気がする。(定義だと入力は自然数だから)
もっとmap
どういうことなの……
ghci> :t map (+) [0..] map (+) [0..] :: (Num a, Enum a) => [a -> a]
型はリストで…リストの要素は(a->a)だから引数をひとつ取る関数が入ってる。
ghci> :t (map (+) [0..]) !! 1 (map (+) [0..]) !! 1 :: (Num a, Enum a) => a -> a ghci> ((map (+) [0..]) !! 1) 1 2
(+)だけmapするとリストの各要素に部分適用されるのか。
(前略)2変数の関数に1引数だけを与えたら、1引数を取る関数が返されるのでした。ゆえに、[0..]を*でmapしたら、1引数関数のリストが得られることになります。(72p)
だから、+でmapしたらリストの中は、
[(+ 0), (+ 1), (+ 2), (+ 3) ...]
って関数のリストになってるのか。
関数のリストって発想はなかった。
いまさらだけど、!!はリストの要素をインデックスを指定して取り出す演算子か。
ghci> [0..] !! 5 5 ghci> :t (!!) (!!) :: [a] -> Int -> a
ラムダ式
さっきラムダ式で定義できるっぽいとかいってたのを定義することにする。
ghci> product (takeWhile (< 10000) (filter (\n -> (n `mod` 3) == 0) (map (^3) [1..]))) 1339176927923476992000
ラムダ式でペアとか取り出せる(パターンマッチできる)けど、パターン網羅はできないとのこと。
ラムダ式よりセクションのほうが短いけど読みやすい。リスト内包よりfilterの方が長いけど読みやすいみたいな不思議。多分、括弧やら->やら記号が多いと読みづらい。
本に載ってるaddThree'の定義で括弧を付けるとこんなふうになると思う。
addThree' :: Int -> Int -> Int -> Int addThree' = \x -> (\y -> (\z -> x + y + z))
返り値が関数なんだわな。
flip'は確かにラムダ式を使ったほうが読みやすい。関数を返すってことがよく分かる。
++をflipするのは場合によって役に立つって感じっぽいけど、subtractをflipするのは一つ目の引数から2つ目の引数が引かれるから、使い所ありそう。
この章、結構ハードな上に長いので今日はこのへんにしとこう。