トップページへ

型クラスの定義と型クラスのインスタンスの定義について

Access Count : 10903

Copyright © 2021 TAKEHANA TADASHI
更新版著作日時: 2021.10.09.土. 09:53:00 著作者、竹花 忠
型クラスの定義と型クラスのインスタンスの定義について:
 classから始まる文で、型クラスを定義する。そこにメソッドを装備しておく。
 また、その型クラスに所属するインスタンスとして、instanceで始まる文で、その型クラスに所属する型を定義する。そこに必要に応じてメソッドの実装を記述してあげる。
 そうすることで、インスタンスに指定された各データー型のデーターに対して、先に装備しておいた各メソッドが使用できることとなる。
 たとえば、下記のコードが、Hankeiという名前の型クラスの定義である。
 そこに、chokkei, enshuu,enmensekiというメソッドを装備している。
class (Num a) => Hankei a where
chokkei :: a -> a
chokkei x = x * 2
enshuu :: a -> a
enmenseki :: a -> a
 そして、Hankeiという型クラスに所属するインスタンスとして、下記のコードにてFloatを指定した定義を行っている。
 型クラスHankeiでは、enshuu,enmensekiについては、型宣言だけであったので、ここで実装を記述することが必要である。なので、その記述も合わせて行っている。
instance Hankei Float where
enshuu x = x * 2.0 * 3.14
enmenseki x = x * x * 3.14
 そうすることで、インスタンスに指定されたFloat型のデーターに対して、型クラスのHankeiが装備しているメソッド、chokkei,enshuu,enmensekiのどれでもが使用できる。
print $ chokkei 3.3
print $ enshuu 3.3
print $ enmenseki 3.3
のように使用すれば、それぞれ、各メソッドの返り値が、3.3 * 2(つまり、6.6), 3.3 * 2.0 * 3.14(つまり、20.724), 3.3 * 3.3 * 3.14(つまり、34.1946)なので、
6.6
20.724
34.1946
と表示される。

 型クラスHankeiのインスタンスの定義として、Int型を指定したものも、用意すれば・記述すれば、型クラスHankeiは、Float型にもInt型にも、メソッドchokkei,enshuu,enmensekiを提供することとなる。
 つまり、型クラスHankeiのインスタンスとして定義したデーター型の集まりに対して、その各データー型のデーターに使用できるメソッドが用意される。
 そのために・それを実現するために、型クラスの定義と型クラスのインスタンスの定義がある。

 型クラスの定義の基本的な構成は下記の通りである。
 型クラスはメソッドを装備している。下記のHankeiという型クラスでは、chokkei,enshuu,enmensekiという3個のメソッドを装備している。
 型クラスの定義は、classで始まって、次に、型クラス名、そして、型変数、さらに、whereで1行目が終わる。
 下記のコードの、Hankeiが型クラス名、aが型変数、である。
 この型変数aは、型クラスに所属するインスタンスを、一括して・類型的に・総称的に・抽象的に、表したものである。
 つまり、登録した型クラスHankeiに所属するインスタンスの型は通常、複数存在する。なので、所属するインスタンスのどれもを表すものとして、型変数aが用られている。
 ただしこの例では、classとその次の型クラス名Hankeiの間に、(Num a) =>が、割り込んでいる・挿入されている。
 これは型クラス制約と呼ばれるものである。
 型変数、に置き換え可能な型を・の取り得る型を、限定するためのものである。
 (Num a) =>なので、=> の後続のaに置き換えられる型は、Num型クラスに所属するインスタンスであるInt型やInteger型やFloat型やDouble型などに限定している。
 型クラス制約が追加されている構文であるので、その分、少し高度な型クラスの定義の記述例である。下記の型クラスの定義は。
 なお、型クラス名は、その先頭文字を大文字にして、任意の名前を付ける。
 2行目以下には、メソッド名から始まる、その型クラスが装備するメソッドの型宣言を記述することが必須となる。
 そしてまた、望むなら・必要なら、メソッドの型宣言だけでなく、メソッドの定義も記述する。
 メソッド名には、その先頭文字を小文字にして、任意の名前を用いる。
 たとえば、型クラスの定義は下記のようになる。

class (Num a) => Hankei a where
chokkei :: a -> a
chokkei x = x * 2
enshuu :: a -> a
enmenseki :: a -> a

 上記の定義においては、Hankeiが型クラス名。
 aが型変数である。
 chokkei, enshuu, enmensekiがそれぞれ、メソッド名である。
 つまり、この型クラスHankeiは、上記の3個のメソッドを装備したものである。
 chokkei :: a -> aは、メソッドchokkeiの型宣言である。
 chokkei x = x * 2は、メソッドchokkeiの定義である。
 enshuu :: a -> aは、メソッドenshuuの型宣言である。
 enmenseki :: a -> aは、メソッドenmensekiの型宣言である。
 ここ型クラスの定義のところでは、enshuu, enmensekiについては、型宣言だけしかしていない。
 なので、この2つのメソッドについては、型クラスのインスタンスの定義のところで、各メソッドの定義を必ず行うことになる。
 ちなみに、型クラスの定義は、classで始まる上記のコード例のようなものである。型クラスのインスタンスの定義は、instanceで始まる。
 上記の例では、いずれのメソッドの型宣言も同一であった。
 a -> aなので、メソッドの引数の型が型変数aの型で、返り値の型も型変数aの型である。つまり、引数の型と返り値の型が同一ということであった。
 なお、結論を言うと、メソッドの引数になる型は、インスタンスの型である。
 つまり、型クラスのインスタンスの定義におけるインスタンスの型が、メソッドの引数である型変数aの型である。
 型クラスのインスタンスの定義が、instance Hankei Float whereで始まっていたならその時には、型変数aにFloat型が当てはまる。
 つまり、class (Num a) => Hankei a whereの型変数aの型はFloat型となる。
 だから、instance Hankei Int whereの時には、
class (Num a) => Hankei a whereの型変数aの型はInt型となる。
 つまりこの件では、2種類の定義の一方の値が他方に反映される。
 具体的には、インスタンスの型が、他方の定義の型変数aの値となる。
 型クラスの定義の型変数と、型クラスのインスタンスの定義のインスタンスの型とは、照応し合っているというか補完し合っているというか、相互に対応し合っている。
 また、型クラスの定義のところでのメソッドについての記述と、型クラスのインスタンスの定義のところでのそのメソッドについての記述も、補完し合っている。
 つまり、この2種類の定義は相互に独立していなくて、相互に関係し合っている。
 なので、型クラスの定義と型クラスのインスタンスの定義とを、セットにして説明を進める。

 ところで、型クラスの定義で型宣言したメソッドは、型クラスのインスタンスの型のデーターに対して使用できるようになる。
 だから、ある型クラスのインスタンスの定義が、複数のインスタンスについて行ってあれば、それらのインスタンスの型のどれものデーターに、そのある型クラスが装備しているメソッドのすべてが使用できる。

 さて話を、型クラスのインスタンスの定義へと進める。
 ちなみに、型クラスのインスタンスの定義は、instanceで始まって、次に、対応する型クラス名、そして、その型クラスのインスタンス、さらに、whereで1行目が終わる。
 つまり、型クラスのインスタンスの定義では、どの型クラスのどんなインスタンスについての定義であるかを、instanceで始まる1行目に示す。
 たとえば、instance Hantei Float whereというのが1行目であるなら、これは、Hantei型クラスの、Float型をインスタンスとする時の定義が開始されたことを表す。
 また、2行目以下には、必要に応じてメソッドの定義を行う。1行目に型クラスが登録されている。その型クラスの定義のところで型宣言のされていたメソッド、に対して、必要に応じて定義や再定義を行うわけである、2行目以下では。
 2行目以下には、型クラスの定義のところで、型宣言だけしかなされていなかったメソッドについてその定義を記述する。
 あるいは、型クラスの定義のところで、メソッドの定義も行っていたにしても、その定義と異なる処理を、このインスタンスの型のデーターに対して実行したい時に、独自の定義をここで再度行う。
 下記の例では、独自の定義を再度行うことはしていないが、型宣言だけで済ませてあったメソッドについては、その定義を記述している。
 つまり、メソッドenshuu、メソッドenmensekiは、ここで初めて定義を行っている。
instance Hankei Float where
enshuu x = x * 2.0 * 3.14
enmenseki x = x * x * 3.14

 なお、ここで、メソッドの定義を記述する際には、型宣言の記述は行わない。
 メソッドの型宣言は、型クラスHankeiの定義のところで済んでいる。
 なお、ここでは、メソッドchokkeiについて再定義は行っていない。
 なので、型クラスHankeiのインスタンスFloat型のデーターに対してメソッドchokkeiを使用した場合には、型クラスHankeiのところのメソッドchokkeiの定義が使用される。
 メソッドchokkeiの定義は、型クラスHankeiの定義のところだけにしか存在しないのだから。
 メソッドenshuu、メソッドenmensekiについては、型クラスHankeiのところで型宣言しただけなので、それらの定義は、ここ型クラスHankeiのインスタンスFloat型の定義のところで行っている。
 であるから、Float型のデーターに対してenshuuを使用した場合には、型クラスHankeiのインスタンスFloat型の定義のところで記述した、メソッドenshuuの定義が使用される。
 つまり、instance Hankei Float where以下で記述したメソッドenshuuの定義が使用される。
 また、Float型のデーターに対してenmensekiを使用した場合には、型クラスHankeiのインスタンスFloat型の定義のところで記述したメソッドenmensekiの定義が使用される。
 つまりやはり、instance Hankei Float where以下で記述したメソッドenmensekiの定義が使用される。
 ところで、同じ型クラスHankeiについてもう一つ別のインスタンスを定義する。
 今回は、Int型を、型クラスHankeiのインスタンスとしての定義を行う。
instance Hankei Int where
enshuu x = round $ fromIntegral x * 2.0 * 3.14
enmenseki x = round $ fromIntegral x ^ 2 * 3.14

 やはり、ここでメソッドの定義を記述する際には、型宣言の記述は行わない。
 メソッドの型宣言は、型クラスの定義のところで済んでいる。
 この例でも、メソッドchokkeiについては再定義を行っていない。
 なので、Int型のデーターに対してchokkeiを使用した場合にも、型クラスHankeiのところでのメソッドchokkeiの定義が使用される。
 メソッドenshuu、メソッドenmensekiについては、型クラスHankeiのところでは、型宣言だけしか行っていない。
 なのでやはり、メソッドenshuu、メソッドenmensekiについて、その定義を、ここにおいて行っている。
 であるから、Int型のデーターに対してenshuuやenmesekiを使用した場合には、ここで記述した、メソッドenshuuの定義やメソッドenmensekiの定義が使用される。
 なお、fromIntegralは、引数の整数を数に変換する関数である。
 つまり、fromIntegral xによって、整数xを、小数点演算が可能な数に変換している。
 roundは、引数の小数点数を、その小数点数に最も近い整数に変換する関数である。

 さて、以上の3つの定義セットによって、型クラスとして、Hankeiが定義され、そして、型クラスHankeiのインスタンスとして、Float型とInt型が、登録され定義された。
 これによって、Float型とInt型は型クラスHankeiのインスタンスになった。なので、Float型とInt型のデーターには、型クラスHankeiで宣言したメソッドが使用可能になった。
 つまり、Float型やInt型のデーターには、メソッドchokkeiやメソッドenshuuやメソッドenmensekiが使用可能になった。
 つまりたとえば、下記のようにプログラムする。
main = do
let
hankeiI = 3 :: Int
hankeiF = 3.3 :: Float

print $ chokkei hankeiI
print $ enshuu hankeiI
print $ enmenseki hankeiI

print $ chokkei hankeiF
print $ enshuu hankeiF
print $ enmenseki hankeiF



class (Num a) => Hankei a where
chokkei :: a -> a
chokkei x = x * 2
enshuu :: a -> a
enmenseki :: a -> a


instance Hankei Float where
enshuu x = x * 2.0 * 3.14
enmenseki x = x * x * 3.14


instance Hankei Int where
enshuu x = round $ fromIntegral x * 2.0 * 3.14
enmenseki x = round $ fromIntegral x ^ 2 * 3.14

 以上のようにプログラムすると、その実行結果は、
6
19
28
6.6
20.724
34.1946
となる。
chokkei hankeiIが6で、
enshuu hankeiIが19で、
enmenseki hankeiIが28で、
chokkei hankeiFが6.6で、
enshuu hankeiFが20.724で、
enmenseki hankeiFが34.1946であるから。
 それらをそれぞれprint関数で表示させているので、上述の実行結果となる。