Haskell における id の使いどころ
問題
Quiz これってどういう関数 - HaHaHa!(old) - haskell
以下のように定義された関数 foo ってどういう関数でしょうか.
uncurry も id も Prelude で定義されています.
haskell foo = uncurry id
処理系に尋かずに想像してみてください.型を考えれば想像できます.想像できたら実際にその通りかどうか試してみてください.
解答
id :: a -> a id a = a uncurry :: (a -> b -> c) -> (a, b) -> c uncurry f = (x, y) -> f x y
関数の定義は上の通り。uncurry
に id
を適用した様子を考える。
(x, y) -> id x y
= { 関数適用が最も高い優先順位 }
(x, y) -> (id x) y
= { id の定義より id x = x }
(x, y) -> x y
= { y の型は b, 返り値の型は c, よって x の型は b -> c }
uncurry id :: (b -> c, b) -> c
つまり最後のパートで型推論が効くということ。
それで id はどこでどう使えるのか
実例を見てみよう。Control.Monad
の関数 ap
は次のように定義されている。
ap :: (Monad m) => m (a -> b) -> m a -> m b ap = liftM2 id
liftM2
の定義はこう。
liftM2 :: (Monad m) => (a1 -> a2 -> r) -> m a1 -> m a2 -> m r liftM2 f m1 m2 = do x1 <- m1 x2 <- m2 return (f x1 x2)
もし id
の正しい読み解き方を知らないと、型 a -> a
の関数を a1 -> a2 -> r
に渡して???となるだろう。
liftM2 id
の型がどうなるか考えてみよう。最初の問題と同じように考えられます。
解答
liftM2 id m1 m2 = do x1 <- m1 x2 <- m2 return (id x1 x2)
return (id x1 x2)
に注目すると id x1 = x1
なので、
return (x1 x2)
x2
の型は a2
なので m2
の型は m a2
x1
は a2
を受け取って c
を返す関数 a2 -> c
x1
をモナドでくるんでいたのが m1 :: m (a2 -> r)
liftM2 id :: m (a2 -> r) -> m a2 -> c
つまり liftM2 id
とすると m1
の型がいい感じに制限されるということですな〜
ここまで、以下のブログを読んで自分なりの再編成でした。
小ネタ
const id
の型を考えよう。どのように動作するのか予想しよう。
const :: a -> b -> a const x y = x
解答
少しフォーマルに考えてみる。
Haskell の関数はすべて型付きのラムダ計算として考えられるはずである。
const x y = x const = \x y -> x const = (\x (\y -> x))
また id = (\z -> z)
このラムダ式を必要呼び出しに従って簡約すればよい。
まず定義をそのまま置換する。
const id === (\x (\y -> x)) (\z -> z)
(\x -> (\y -> x)) (\z -> z) = { β簡約 } (\y -> (\z -> z)) = { 型は b -> a -> a }
あまりに簡単すぎて牛刀で鶏を裂いている感じがするが、関数が何をしているのか分からなくなったらラムダ計算をこつこつやるのが理解の助けになる、ということが分かると思う。
ちなみに const id
を使うと、畳み込みのポイントフリーで last
が実装できて便利ですね。
last' = foldr1 (const id)