2010年2月28日日曜日

Haskellにおける型と"type class"

Scalaとかいう汚い言語を勉強した反動で、美しい言語に触れたくなり、Haskellを触っていた週末でした。

さて、Haskellには "class", "data", "instance", "type" という予約語がありますが、一般的な(オブジェクト指向言語における)意味と全然違うので注意が必要です。

普通に考えると、
  • class: クラス定義?
  • data: インスタンス生成?
  • instance: dataと同じ・・・?
  • type: ???
というように使えそうですが、CやJavaの言葉で説明すると、
  • class: インターフェイス定義用。Javaのinterfaceに近い。
  • data: 構造体定義用。Cのstructに近い。
  • instance: 構造体に属性を定義する。強いて言えばJavaのimplementsに近い。
  • type: 型の同名 (synonim) を作る。Cのtypedef。
となります。

例としてBookという型を考えます。
data Book = Book {
  title :: String,
  authors :: [String],
  isbn :: Int
  }

b = Book "Enjoy Haskell" ["Alice", "Bob"], 12345678
Javaで書くとこう(動くことを確認していません。最近Java触って無いので間違ってるかも)。
class Book {
  public String title;
  public ArrayList authors;
  public int isbn;
}

Book b = new Book;
b.title = "Enjoy Haskell";
b.authors = new ArrayList; // あと省略
b.isbn = 12345678;
ISBNがintで表現できるのかという問題は無視します。)
これでBookクラスを定義できましたが、これだけだといろいろ不便です。例えば(非常に恣意的な例ですが・・・)大小判定ができません。ghciで試してみると・・・
*Main> let b1 = Book "Enjoy Haskell" ["Alice", "Bob"] 1234
*Main> let b2 = Book "Scala Sucks" ["Charlie", "Daniela"] 5678
*Main> b1 < b2

:1:0:
    No instance for (Ord Book)
      arising from a use of `<' at :1:0-6
    Possible fix: add an instance declaration for (Ord Book)
    In the expression: b1 < b2
    In the definition of `it': it = b1 < b2
と、エラーになります。
ご丁寧に"Possible fix: add an instance declaration for (Ord Book)"と、修正方法を教えてくれました。instance declarationを追加してみます。
instance Eq Book where
  (==) a b = (isbn a) == (isbn b)

instance Ord Book where
  compare a b = compare (isbn a) (isbn b)
(今必要なのは、Ord Bookの宣言ですが、OrdはEqのサブクラス(のようなもの)なので、Eq Bookも定義しなくてはいけません。)
これで、大小比較できるようになります。

Javaで同じ事をやるには、Comparableというインターフェイス(で定義されているcomparaメソッド)を実装します。
class Book implements Comparable {
  public int compare(Object o) { return isbn - (Book o).isbn; }
  public String title;
  // 省略
}

というように、Javaでimplementsを使うべきところ、つまりクラスにある性質を追加したいときに、Haskellではinstanceを使います。そして、EqやOrdを"type class"と呼びます。で、type classを定義するのに使う予約語が"class"です。例えばEqとOrdの定義はこうです。
class Eq a where
  (==) :: a -> a -> Bool
  (/=) :: a -> a -> Bool

class (Eq a) => Ord a where
  compare :: a -> a -> Ordering
  (<) :: a -> a -> Bool
  (>=) :: a -> a -> Bool
  (>) :: a -> a -> Bool
  (<=) :: a -> a -> Bool
  max :: a -> a -> a
  min :: a -> a -> a
type classが持つべきメソッドの型を定義しています。Javaだとこうですね。
interface Comparable extends Eq {
  // "extends Eq"はHaskellとの比較のためにつけただけで、実際には不要。

  int compare(Object o);
}

このように、"class", "data", "instance"をJavaと対応付けることで、理解しやすくなると思います。

最後に残った"type"ですが、これはCのtypedefと同じです。
type ErrorNo = Int
Cではこうですね。
typedef int ErrorNo;
別名をつけただけなので、型チェックでは同じ型だとみなされるのもCと同じです。下のコードはtype checkをパスします。
f :: ErrorNo -> String
f e = "Hello World!"

g :: Int -> String
g i = f i

ということで、Haskellにでてくる型関連の予約語を整理してみました。

0 件のコメント:

コメントを投稿