多重定義

関数は、型が合ってないと使えないんだけど、違う型でも同じように使えると便利なものもあるよね。

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