Haskellと型

Haskellではデータは全て型を持っています。この型のことを代数的データ型と言います。Int, Integer, Float, Doubleなどがあります。

変数は

a :: Int
a = 3

のように定義します。関数は

add :: Int -> Int -> Int
add x y = x + y

のように定義します。これはaddという関数は引数xとyをとり、x + yの結果を返すことを意味します。型は一番左のIntが最初の引数xの型、2番目のIntが2番目の引数yの型、最後のIntが戻り値の型です。変数と関数が同じ名前空間に束縛されるので変数と関数に同じ名前は使えません。

新しい代数的データ型は既存の代数的データ型を組み合わせて

data Figure = Triangle {
                base :: Double,
                height ::Double
              }

のように定義します。Figureは型の名前で、これをタイプコンストラクタと言います。Triangleはデータの名前で、これをデータコンストラクタと言います。base :: Doubleはbaseというラベルを持つDouble型のフィールドです。名前が必要なければ

data Figure = Triangle Double Double

と書けます。代数的データ型は複数のデータコンストラクタを持つことができて

data Figure = Triangle Double Double
            | Rectangle Double Double
            | Circle Double

のように書けます。これはFigure型はTriangle、Rectangle、Circleのいずれかのデータを持つことを意味します。データはフィールドが無くても構いません。その場合は

data Bool = True | False

のようになります。この場合、フィールドを持たないデータコンストラクタTrueとFalseができることになります。

型変数を持つ代数的データ型は

data Maybe a = Nothing | Just a

のように書けます。使うときにaに具体的な代数的データ型を入れて使います。

value :: Maybe Int
value = Just 2

これはvalueはMaybe Int型で、具体的なInt型の値を伴ったJust、もしくはNothingを取ることを意味します。

これらの代数的データ型はIntなどと同じレベルのものですから、同じように使うことができます。

f1 :: Figure
f1 = Triangle 3 4
area :: Figure -> Double
area (Triangle base height) = base * height / 2

f1の型はタイプコンストラクタで指定します。f1に入れる値はデータコンストラクタにフィールドの値を指定して作ります。area関数の型もタイプコンストラクタで指定します。area関数の引数は括弧でくくってデータコンストラクタ、フィールド数と同じ数の仮引数で受けます。Figure型の他のデータも同じように書けます。

area (Rectangle width height) = width * height
area (Circle radius) = radius ^ 2 * pi

このようにしておくと、適切なデータコンストラクタを選択して実行してくれます。

このようにして特定の代数的データ型と密接な関係を持った関数を定義することができます。

この他に型クラスを使う方法もあります。

class Dimension2 a where
  area2 :: a -> Double

instance Dimension2 Figure where
  area2(Triangle base altitude) = base * altitude / 2
  area2(Rectangle width height) = width * height
  area2(Circle radius) = radius ^ 2 * pi

最初のclass宣言でDimension2クラスはarea2というメソッドを持つことを宣言します。このメソッドはなんらかの型を受け取ってDouble型を返すことを意味します。なんらかの型は次のinstance宣言で指定されます。

instance宣言ではDimension2クラスを代数的データ型Figureが実装することを意味します。class宣言でなんらかの型となっていた部分にFigure型が入ることになります。whereの後にarea関数と同じようにメソッドを宣言します。この場合、FigureはDimension2のインスタンスであると言います。

同じように見えますが、クラスは複数の代数的データ型が実装できることが違います。複数の代数的データ型があるクラスを実装していた場合、型クラス制約を付けることができます。

doubleArea :: Dimension2 a => a -> Double
doubleArea a = area2 a * 2

これでDimension2クラスを実装している代数的データ型がdoubleAreaを呼べるようになります。この最初の宣言はdoubleAreaがDimension2クラスのインスタンスであるなんらかの代数的データ型aを受け取ってDoubleを返す型であることを宣言しています。次の行はDimension2のインスタンスであるなんらかの代数的データ型を受け取って、それに紐づいているarea2メソッドを呼んで2倍する関数です。

Haskellではこのように代数的データ型に対して型クラスを用いてメソッドを紐づけていきます。型クラスはデフォルト実装を持てたり、継承できたりと便利になっています。

最後に文中で使ったコードのまとめです。

data Figure = Triangle Double Double
            | Rectangle Double Double
            | Circle Double

class Dimension2 a where
  area2 :: a -> Double

instance Dimension2 Figure where
  area2(Triangle base altitude) = base * altitude / 2
  area2(Rectangle width height) = width * height
  area2(Circle radius) = radius ^ 2 * pi

area :: Figure -> Double
area (Triangle base altitude) = base * altitude / 2
area (Rectangle width height) = width * height
area (Circle radius) = radius ^ 2 * 3.14

doubleArea :: Dimension2 a => a -> Double
doubleArea x = area2 x * 2

t = Triangle 3 4
r = Rectangle 2 4
c = Circle 2

main = do
  print $ area t
  print $ area r
  print $ area c
  print $ area2 t
  print $ area2 r
  print $ area2 c
  print $ doubleArea c

結果

$ runghc figure_blog.hs
6.0
8.0
12.56
6.0
8.0
12.566370614359172
25.132741228718345

areaとarea2で円周率を変えてあるので、きちんと別の呼び出しがされているのがわかると思います。

コメント

このブログの人気の投稿

五十音配列付き新下駄配列

WSLでの親指シフトはどうやらMozcで実現可能と気がつくまで

親指シフト新下駄配列の可能性