タイプクラスとインスタンス
Haskell を学んでいて、訳がわからなかったのが、(タイプ)クラスとインスタンスの意味だった。
第1に手続きがあまりにも煩雑過ぎる。class キーワードでクラス名を宣言し、where 以下でそのクラスの関数の型を宣言する。次に instance 宣言でデータ型をそのクラスのインスタンスとして宣言し、そのデータ型に対応するクラスの多相関数の実装を行う。
第2にその煩雑な手続きが一体何の目的で使われるのかがわからない。
これは、ひとつには、オブジェクト指向言語のクラスとインスタンスの類推から、Haskell のクラスとインスタンスの意味を推定しようとしたために混乱したのではないかと思う。両者には全く共通点はない。オブジェクト指向言語のクラスのことは全く忘れて Haskell のクラスに取り組まなくてはならない。
一口に言うと、タイプクラスとインスタンスの仕組みは、要するに演算子や関数の overload (関数や演算子の多重定義)を行うための工夫だ。
Haskellの関数は強く型付けられた関数なので、同じ関数について型の異なる引数について多重定義しようとすると、次のように Conflicting definitions コンパイルエラーになる。
このようなときは、タイプクラス Plus a を宣言して、Int 型や Double 型を Plus のインスタンス(タイプクラスに属するデータ型)に宣言することで、関数 plus が Int 型にも、Double 型にも使えるように関数 plus を overload (多重定義) することができる。
タイプクラスによる多相関数の実現といってもこれだけのことだ。しかし、いろいろな型に同じ名前の関数が使えるというということの利点は、多相関数という関数によって、抽象的な機能が実現できることだ。
上の例ではその有り難みはあまり感じられないかもしれないが、例えば head 関数のことを考えてみると分かる。head は数値のリストであろうが、文字のリストであろうが、タプルのリストであろが引数がリストであればその先頭の要素を取り出す事ができる。「リストの先頭の要素を取り出す」という抽象的な操作が、どんなにありがたいものかはプログラミングしてみると分かる。
Haskell の最大の強みは抽象的なプログラムができるということだ。そのための武器が、関数が first order value であることと、多相関数が使えるということだ。
Haskell のプログラミングにおいて、タイプクラスは必ずものにしておくべき知識だ。
タイプクラスとインスタンスの定義
Prelude> :set +m
Prelude> let
Prelude| plus :: Int -> Int -> Int
Prelude| plus x y = x + y
Prelude| plus :: Double -> Double -> Double
Prelude| plus x y = x + y
Prelude|
:9:1:
Duplicate type signature:
:9:1-25: plus :: Int -> Int -> Int
:11:1-34: plus :: Double -> Double -> Double
:10:1:
Conflicting definitions for `plus'
Bound at: :10:1-4
:12:1-4
Prelude> class Plus a where
Prelude| plus :: Plus a => a -> a -> a
Prelude|
Prelude> instance Plus Int where
Prelude| plus x y = x + y
Prelude| instance Plus Double where
Prelude| plus x y = x + y
Prelude|
Prelude> plus (2::Int) (3::Int)
5
Prelude> plus 2.0 3.0
5.0