bitterharvest’s diary

A Bitter Harvestは小説の題名。作者は豪州のPeter Yeldham。苦闘の末に勝ちえた偏見からの解放は命との引換になったという悲しい物語

HaskellドリルⅠ 独自の新しい型を定義しよう

1.新しい型を作成する-data

Haskellが用意している基本的な型だけでは、分かりやすいプログラムをかけないことが多い。例えば、図形を描くプログラムを開発しようとするとき、点や線や円、はたまた、三角形や四角形などを型として表したいという欲求が生じる。

このような時、Haskellはdataというキーワードによって、独自の型を作成することを許している。例えば、点Pointという型は、x座標とy座標でのそれぞれの値として表されるので、次のように表すことができる。

data Point = Point Double Double deriving (Show)

上記で、等号の左にあるPointは型コンストラクタで、右にあるPointは値コンストラクタである。同じ名前で役割が違うため少し紛らわしいが、もう少し後の説明でこのことは明確になる。また、dataは型を定義しているので、定義した型は型クラスのインスタンスになることができる。どの型クラスのインスタンスであるかは、derivingによって示す。上記の場合には、型クラスShowのインスタンスとなっている。derivingを用いてインスタンス化することをインスタンスの自動導出という。

それでは図形Shapeを型にしてみる。図形にはいろいろな種類があるが、ここではとりあえず、円Circle、三角形Triangle、直方形Rectangleを扱うことにする。円は中心と半径、三角形は頂点の三点、長方形は左下と右上の点で表すことにすると、次のようになる。

data Shape = Circle Point Double | Triangle Point Point Point | Rectangle Point Point deriving (Show)

上記で、Shapeは型コンストラクタで、Circle, Triangle, Rectangleは値コンストラクタである。以前の記事でも説明したが、値コンストラクタは関数である。例えば、Circleの型シグネチャを見ると次のようになっている。

Prelude> :t Circle
Circle :: Point -> Double -> Shape

上記より、CircleはPointとDoubleを入力として受理し、Shapeを出力として送出していることが分かる。従って、円を作成するためには、中心点Pointと半径Doubleを入力すればよいので、例えば、中心の座標が(3,4)、半径が0.5の円は次のように定義できる。

Circle (Point 3 4) 0.5

これまで説明してきた値コンストラクタはフィールドを有していた。例えば、Circleであれば、PointとDoubleの二つのフィールドを有していたが、もちろんフィールドのないものも許される。

例えば、曜日Dayを表す型を用意すると次のようになるであろう。

data Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday deriving (Show)

2.分かりやすい型に-レコード型

先ほどのShapeという型は、三つの値コンストラクタCircle, Triangle, Rectangleで構成されていた。それぞれのコンストラクタはフィールドを有していた。Circleであれば、Point,Doubleである。プログラムを作成した頃は、これらのフィールドが何を表していたかを理解しているが、時が経つにしたがって、記憶が曖昧になることは否めない。

そこで、フィールドに名前を付けたのが、レコード構文である。Shapeという型は次のように定義できる。

data Shape = Circle {center :: Point, radius :: Double} | Triangle {vertex1 ::Point, vertex2 :: Point, vertex3 :: Point} | Rectangle {leftBottom :: Point, rightTop :: Point} deriving (Show)

これを用いて、中心の座標が(3,4)、半径が0.5の円は次のように定義できる。

Circle {center = Point 3 4, radius = 0.5}

3.カメレオンのような型に-型変数

今まで、Shapeという型は、座標や長さ(半径)を与えるときに、Doubleという型を用いて定義した。しかし、場合によっては、整数だけを使って表したい場合もあるし、浮動小数点数を用いて表したい場合もあるし、ことによっては、有理数で表したい場合もあるかもしれない。このように、多様な型を利用できるようにするために、型変数を用いる。

例えば、点の座標が、整数、浮動小数点数有理数などをとれるようにするためには、次のように定義する。

data Point a = Point a a deriving (Show)

上記のプログラムで、aを型変数という。変数というからには、後で、値が定まることになるが、それは型である。即ち、Int, Integer, Float, Doubleなどであったりする。

例えば、座標にいろいろな値を入れて、型変数がどの型に束縛されてたかを見ていくと次のようになる。

Prelude> data Point a = Point a a deriving (Show)
Prelude> let p1 = Point 3 4
Prelude> :t p1
p1 :: Num a => Point a
Prelude> let p2 = Point 3.0 4.0
Prelude> :t p2
p2 :: Fractional a => Point a
Prelude> :t p3
p3 :: Fractional a => Point a
Prelude> let p4 = Point (sqrt 2) (sqrt 5)
Prelude> :t p4
p4 :: Floating a => Point a

それでは、Shapeも様々な型がとれるように定義しなおすと次のようになる。

data Shape a = Circle {center :: Point a, radius :: a} | Triangle {vertex1 ::Point a, vertex2 :: Point a, vertex3 :: Point a} | Rectangle {leftBottom :: Point a, rightTop :: Point a} deriving (Show)

中心の座標が(3,4)、半径が0.5の円を定義して、その型シグネチャを見ると次のようになる。

Prelude> :t c1
c1 :: Fractional a => Shape a

座標も半径もFractionalに束縛されていることが分かる。

4 問題

それでは、問題を解いてみよう。

問題1:型変数を利用しないで、2次元ベクトルVector2を新しい型として定義しなさい。

問題2:型変数を利用して、2次元ベクトルVector2’を新しい型として定義しなさい。

問題3:上記を用いて、2次元ベクトルに関連する演算、ベクトルの和、ベクトルの差、内積(スカラー積)、外積(ベクトル積)など、を定義しなさい。

問題4:型変数を利用しないで、2次元行列Matrix2を新しい型として定義しなさい。

問題5:型変数を利用して、2次元行列Matrix2’を新しい型として定義しなさい。

問題6:上記を用いて、2次元行列に関連する演算、行列の和と積など、を定義しなさい。

問題7:型変数を利用しないで、正多角形RegularPolygonを新しい型として定義しなさい。この時、値コンストラクタは、正三角形EquilateralTriangle、正方形 Square、正五角形 RegularPentagon、正六角形 RegularHexagonとする。なお、それぞれのフィールドは中心と一片の長さを有するものとし、型の定義はレコード構文を用いて行うこと。

問題8:型変数を利用して、上記の正多角形RegularPolygon’を新しい型として定義しなさい。

問題9:上記を用いて、正多角形に関連する演算、周囲の長さ、面積、外接する円の半径、内接する円の半径など、を定義しなさい。

問題10:テレビの番組を新しい型として定義しなさい。その時、フィールドは番組名、ジャンル、放送日を有するようにしなさい。