Life Goes On

まあまあだけど楽しんでる方です

リストも関数も同じもの

注:この文章は Haskell 布教のため、 Haskell を知らない人向けに書いています。なので、ちょっとでも分かりにくいところはどんどん指摘してください。

しばらく前に、hasekll-jp のメーリングリストHaskell の型クラスの体系が話題になっていたのですが、そこで衝撃を受けたメールがありました。
http://www.sampou.org/cgi-bin/w3ml.cgi/haskell-jp/msg/463
何に驚いたのかを以下に説明したいと思います。

リストと要素の変更

Haskell にはリストというデータ構造があります。他の言語と同じように特定の種類の要素を複数格納していて、インデックスを指定すると要素を取り出すことができます。

list = [1,5,7,6,2,8,3,0,9,4]  -- 0から1までの整数を格納したリスト
val = list !! 3  -- インデックスを指定して中身を取り出す(この場合は 6 が返る)

また map という関数があって、リストの要素をまとめて変更をすることができます。Ruby なんかにもありますね。

f x = x + 5  -- 引数に 5 を足す関数 f を定義
list' = map f list  -- list の要素それぞれに 5 を足す
val' = list' !! 3 -- 今度は 6 + 5 = 11 が返る

関数と関数の結合

今度は違った例を考えて、引数を 2 倍する関数を定義してみます。

func x = x * 2  -- 引数を 2 倍する関数 func を定義
res = func 3  -- func の引数に 3 を渡す(3 * 2 = 6 が返る)

また Haskell には、複数の関数をつなげて順番に適用する(結合する)関数 (.) があります。

func' = (.) f func  -- func してから f する(2 倍してから 5 を足す)
res = func' 3  -- 3 * 2 + 5 = 11 が返る

Functor と fmap

これまでリストの要素の変更と関数の結合という異なる操作を挙げましたが、実は fmap という関数を使うと、これらは同じように書くことができます。

import Control.Monad.Instances

list' = fmap f list  -- fmap を使って、list の要素を f で変更する
val' = list' !! 3 -- これも 11 が返る
func' = fmap f func  -- fmap を使って、f と func を結合する
res' = func' 3  -- これも 11 が返る

つまり、リストも関数も同じように扱うことができるのです。
なぜこんなことができるかを考えてみます。以下の図のように捉えると、リストも関数も値を格納した箱(コンテナ)であり、インデックスを指定したり引数を与えたりすることで、値を取り出すことができるのだとみなすことができます。

このように何らかの値を格納したコンテナのことを、Haskell では Functor クラスと定義しています。Haskell のクラスは JavaC# でのインタフェースに相当し、必要なメソッド(この場合は fmap)を定義することで、そのクラスのインスタンスとして扱うことができます。
さらに fmap はコンテナの構造を変えずに値を変更する操作だと言えます。僕は関数が Functor クラスとして定義できることを知識としては知っていましたが、関数結合の (.) が fmap で置き換えられることには気づいておらず、とても驚いたのです。
Functor のような型クラスは Applicative、Arrow、モナド など色々あります。実際、モナドは Functor の制約を厳しくしたサブクラスであり、リストに対応するリストモナド、関数に対応する Reader モナドといったインスタンスがあります。
このような抽象化ができると、使う側もこれまでと違った新しい見方ができるようになります。リストと関数を同じように扱える言語なんて、そうそうありません。そこが Haskell の大きな魅力の一つだと思っています。
参考文献: