関数は、型が合ってないと使えないんだけど、違う型でも同じように使えると便利なものもあるよね。
Prelude> True /= False
True
Prelude> 10 /= 20
True
このような関数をクラスメソッドというんだけど、これがどういう風に定義されているか見てみよう。
というわけで、次のソースコードを使って説明するよ。
module TestClassMethod where
data Type1 = A | AA
data Type2 = B | BB
type1 A = 1::Int
type1 AA = 11::Int
type2 B = 2::Int
type2 BB = 22::Int
class MyClass a where
number :: a -> Int
calculate :: a -> a -> Int
calculate x y = (number x) * (number y)
instance MyClass Type1 where
number x = type1 x
instance MyClass Type2 where
number x = type2 x
動作を見る前に、何をしているか説明しておこう。
最初のdata ~の部分は、新しい型を作っているね。
新しい型を作った場合、何もなければ関数を適用できないんだったよね。
それで、次にこの型を使った関数を定義しているんだ。
この関数は型とほとんど同じ名前だけど、最初の字が大文字か小文字かが異なる。
この例だと、単に紛らわしいだけだけど(実際に使う関数を作るときには良くないね)、
新しい語句を見かけたら、最初の字を見て関数か型かを判断してね。
type1とtype2は2回定義されているよね。
これは、パターン照合(パターンマッチング)といって、
型が同じで値が重ならなければ、一つの関数名に複数の定義を当てられるんだ。
まあ、関数は全ての値に対して定義されないとエラーの元になるんだけどね。
このソースはちゃんと定義されているから、次の例でエラーになることを確認しよう。
*TestClassMethod> let bol True = True
*TestClassMethod> bol True
True
*TestClassMethod> bol False
*** Exception: <interactive>:1:4-18: Non-exhaustive patterns in function bol
このエラーメッセージも覚えておこう。
パターン照合は、もっと簡潔に書けるけど、それは、詳しい説明も含めて別の機会に。
その次はクラスの宣言だ。
他のクラスを:iで見たのと同じように書けば、それで宣言になる。
::以下も自分で書くんだよ。これもれっきとしたhaskellの書式の一つなんだ。
クラス宣言では、関数(クラスメソッド)は::で型宣言をしておけばいい。
あと、型が合ってさえいれば、ここで関数の定義も出来る。
そして、その次にインスタンスの宣言をする。
クラスと型を示して、クラスメソッドの、その型に対する定義を書くんだ。
このソースの宣言はそのままだね。
ああ、もちろん、type1とか名前を付けないで、ここに直接numberの定義を書いても問題ない。
むしろそっちの方がいい。ここは説明の順番として、ね?
ともかく、これでクラスメソッドが使えるようになった。試してみよう!
*TestClassMethod> :t number
number :: (MyClass a) => a -> Int
*TestClassMethod> number A
1
*TestClassMethod> number BB
22
宣言した通りだね。:tの見方も理解できるよね。
*TestClassMethod> calculate AA AA
121
*TestClassMethod> calculate B BB
44
*TestClassMethod> calculate A B
<interactive>:1:12:
Couldn't match `Type1' against `Type2'
Expected type: Type1
Inferred type: Type2
In the second argument of `calculate', namely `B'
In the definition of `it': it = calculate A B
ここで注意して欲しいことがある。二つの値が別々の型だと、上手く計算できないんだ。
型宣言を見直そう。
*TestClassMethod> :t calculate
calculate :: (MyClass a) => a -> a -> Int
*TestClassMethod> :t calculate A
calculate A :: Type1 -> Int
*TestClassMethod> :t calculate B
calculate B :: Type2 -> Int
calculateは、一方の型が決まったら、もう一方も型が制限される。
だから、型が異なると計算できない。
まあ、この関数の場合、返り値はIntで統一されているから、上手くやれば違う型でも適用できるようになりそうかな。
これで一応、クラスとクラスメソッドの使い方は分かったね。
実際にクラスを作るときは、
スーパークラスを指定するものだけど、そのやり方はまた今度。
最終更新:2007年09月30日 11:22