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

関数の定義は上の通り。uncurryid を適用した様子を考える。

(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

x1a2 を受け取って c を返す関数 a2 -> c

x1モナドでくるんでいたのが m1 :: m (a2 -> r)

liftM2 id :: m (a2 -> r) -> m a2 -> c

つまり liftM2 id とすると m1 の型がいい感じに制限されるということですな〜

ここまで、以下のブログを読んで自分なりの再編成でした。

Applicative勉強中 - 取り急ぎブログです

小ネタ

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)

99 questions/Solutions/1 - HaskellWiki