前回までのあらすじ
すごいHaskellたのしく学ぼうでHaskellことはじめ3
なんとなーく再帰は書けたかなー?というレベル。
この先生きのこれるのか……。
参考
- すごいHaskellたのしく学ぼう
5章 高階関数
カリー化
- Haskellでは すべての関数が引数を一つだけとる 事になっている。
- ……んでも今まで普通に二つとか引数とってなかったっけ?
max
とか
- ……んでも今まで普通に二つとか引数とってなかったっけ?
max関数
本書より、 max 4 5
の場合……。
- はじめに
max 4
が適用される max 4
の返り値は5 に適用するための別の関数
別の関数 5
が適用され、最終的な値が返る
ふーむ……?
max関数の型はこう。
Prelude> :t max
max :: Ord a => a -> a -> a
こう書く事もできる。 a型の値を引数にとり、a型の値を返す
max :: Ord a => a -> ( a -> a )
関数を本来より少ない引数で呼び出すことを 部分適用する
という。 部分適用
すると、関数をその場で作りだし、それを他の関数に渡せる。
それを踏まえて、max関数。
- はじめに
max 4
が適用される (一つ目のa) max 4
の返り値は 引数を一つとって関数を返す関数 となる ( ( a -> a ) の部分)- 引数を一つとって関数を返す関数 に5が適用され、最終的な値が返る ( かっこの中の a -> a )
これらから、部分適用できる、というのは何となくだけどわかった。これはかけるのだ。
Prelude> let max' x = max 4 x
Prelude> :t max'
max' :: (Num a, Ord a) => a -> a
Prelude> max' 5
5
これがわからない。
Prelude> let max'' = max 4
Prelude> :t max''
max'' :: Integer -> Integer
Prelude> max'' 5
5
やっていることは上と等価なはずなんだけど、関数定義の部分に変数xがいない。
max 4 の返り値が 引数を一つとって関数を返す関数 であるから、max” も引数を一つとれるということ?
セクション
中置関数(divとか)も部分適用ができる。
ん?カリー化どこいったの?
カリー化=部分適用……ではないんだよね? - カリー化談義 - あどけない話
ともあれ、中置関数はこう書く。かっこでくくって部分適用したい方の引数に値を書く。
divideByTen = (`div` 10)
divideByTen' = (10 `div`)
*Main> divideByTen 200 # 200 / 10
20
*Main> divideByTen 10 # 10 / 10
1
*Main> divideByTen' 2 # 2 / 10
5
*Main> divideByTen' 10 # 10 / 10
1
ただし、引き算を使うときだけ注意しなければならない。 (-4)
は マイナス4
として扱われる。(引きたい時は subtract
関数を使う)
関数を表示する
これがどうなのか知りたかった!……んだけど「Show型クラスのインスタンスでないとエラーとなる」だって。
結局「その関数」(今回はdiv)が引数をどう取るか、部分適用して今どういう状態になっているのか(本書の言い方だと、どんな小さい工場になっているか)知らないと、部分適用しているかどうかってわからないって事?
うーん。
高階実演
Haskellでは関数も引数にとれる。同じ関数を二回適用する。
applyTwice f x = f (f x)
*Main> applyTwice tail [1,2,3,4]
[3,4]
f (f x) -> f (tail [1,2,3,4]) -> f ([2,3,4]) -> tail [2,3,4] -> [3,4]
ってことかな。
*Main> applyTwice (++ "Hoge") "A"
"AHogeHoge"
*Main> applyTwice ("Hoge" ++) "A"
"HogeHogeA"
こういうのに対しては部分適用が使われている。
f (f x) -> f ((++ "Hoge") "A" -> f ("AHoge") -> (++ "Hoge") "AHoge" -> "AHogeHoge"
こういうことか!順番あってる?
zipWith
こんな関数。1つの関数と2つのリストを引数に持ち、リストの各要素に関数を適用する。
Prelude> zipWith (+) [1,2,3,4] [6,7,8,9]
[7,9,11,13]
zipWith' _ [] _ = []
zipWith' _ _ [] = []
zipWith' f (x:xs) (y:ys) = f x y : zipWith' f xs ys
flip
これも関数と2つの値をとる。で、値の順番を入れ替える。
*Main> flip (div) 2 0
0
*Main> flip (div) 0 2
*** Exception: divide by zero
*Main> flip (++) [2] [1]
[1,2]
*Main> flip (++) [1] [2]
[2,1]
flip' f x y = f y x
んー何となく書けたんだけど、解説がわからないぞ……。(解説ではxとyが逆になってる)
この新しいバージョンのflip’ では、関数がカリー化されていることをうまく使っています。flip’ f を引数y とx なしで呼び出したら、2 つの引数を取る、引数の入れ替わったf が返るでしょう。
そうなの?
型を調べてみる。
*Main> :t flip'
flip' :: (t1 -> t2 -> t) -> t2 -> t1 -> t
あ、わかったかも。並べて、
flip' :: (t1 -> t2 -> t) -> t2 -> t1 -> t
flip' f x y = f y x
- 最初の引数(f)は
引数を二つ(t1, t2)をとり、関数(t)を返す関数
(div) y x
- 最初の引数(f)に次の引数(x / t2)を適用すると
最初の引数(f)に引数t2を適用した関数を返す関数
を返す(div) y 0
最初の引数(f)に引数t2を適用した関数を返す関数
に次の次の引数(y /t1)を適用すると結果をshowする関数?
を返す?(div) 2 0
= 0
……?
あれ?違うっぽいな……。
続く!