読者です 読者をやめる 読者になる 読者になる

Block Rockin’ Codes

back with another one of those block rockin' codes

Haskell の id の使い方

第1回 スタートHaskell2 : ATND に参加させていただいたんですが、
そこで id なる関数を知りました。
(実は H 本に出ているんだけど、割とさらっと扱われてた。)


最初、これはどんな場面で使うのか分からなかったんですが。
色々ヒントをいただいたりしながら、自分なりに調べてみました。
(@ さんありがとうございます。)

id の型と挙動

まず、 id の型は以下です。

Prelude> :t id
id :: a -> a


とても単純で、「何かの型の値を受け取り、その値をそのまま返す」関数と読めます。


型は多相型で、引数も戻り値も同じ型です。そして、ここには一切の制限がありません。


実は、扱う型について一切の制限が無いというのは、逆を言えば非常に大きな制限で、
それぞれが「どんな型クラスのインスタンスなのか」すらも制限されていないため、
それを扱うことができる関数の中では、「ただそのまま返す」以外のことが出来ないということです。


つまり、この型で宣言される関数は、基本的には「そのまま返す」この id の挙動しかできないそうです。

確かに、例えば関数内でちょっとでも、値の「計算」や「比較」でも行えば、
途端に、引数が Num や Ord の型クラスに属している必要が出てくるため、
型に、型クラスの制約がつきます。


次にこれの使い道をいくつかあげます。

利用例 1 (zipWith)

まずは zipWith の例です。
zipWith の型は以下。

Prelude> :t zipWith
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]

関数と二つの配列を引数にとり、関数を適応した新しい配列を返します。

Prelude> zipWith (+) [1,2,3] [4,5,6]
[5,7,9]

で、適応したい関数を配列に持ち、二個目の配列の各要素に適応する場合、
id を用いて以下のように書けます。

Prelude> zipWith id [(+2), succ, negate] [5,6,7]
[7,7,-7]

利用例 2 (foldl)

foldl は左からの畳み込みを行う関数です。
型は以下。

Prelude> :t foldl
foldl :: (a -> b -> a) -> a -> [b] -> a

畳み込む場合は、アキュームレータの初期値が必要です。
以下は、整数配列を加算で畳込みたいので、初期値を 0 にしています。

Prelude> foldl (+) 0 [1,2,3]
6

foldl で、関数の配列を合成する畳み込みを考えたとき、
id を初期値として使うことができます。

Prelude> let calc = foldl (.) id [succ, negate, (+3)]
Prelude> calc 10
-12

利用例 3 (再帰)

そして、 id は関数合成の単位元とみなすことができるため、
(みなすっていうか、そうなんだと思う。)
あまりいい例では無いかもしれませんが、
関数配列内の関数を合成する関数、を再帰で定義した場合に以下のように使える。

comp :: [(a -> a)] -> (a -> a)
comp [] = id
comp (x:xs) = x . (comp xs)
*Main> let a =comp [(+1), (*2), negate]
*Main> a 10
-19

まとめ

結果から言うと、上の例三つは、結局同じことで、
やっぱり id は関数の単位元で、
それが必要な場面で使えるということだと思う。


もう少し、圏論的な話に進んでいくと、
より一層見えてくるものがあるのかな?