「Learn You a Haskell」の「Making Our Own Types and Typeclasses」の写経 その壱

Haskellで独自のデータ型、型クラスを定義する方法を知りたかったので [Learn You a Haskell for Great Good!](http://learnyouahaskell.com/chapters) の [Making Our Own Types and Typeclasses](http://learnyouahaskell.com/making-our-own-types-and-typeclasses) を写経してます。

今回は **「Algebraic data types intro」** です。

---

独自のデータ型を作る方法のひとつは型の定義に `data` キーワードを使うことです。標準ライブラリの `Bool` 型の定義は以下のようになっています。

```haskell
data Bool = False | Ture
```

`data` は新しい型の定義を意味します。`=` の手前、ここでは `Bool` が**型**であることを示しています。`=` の後ろの部分は **値コンストラクタ** です。定義した型が取りうる異なる値を指定しています。

`|` は `or` と認識できます。なのでここでは `Bool` は `True` もしくは `False` を持つことができるのだと読み取ることができます。型と値コンストラクタはどちらも大文字である必要があります。

似たような例として `Int` は以下のような定義になっていると考えることができます。

```haskell
data Int = -2147483648 | -2147483647 | ... | -1 | 0 | 1 | 2 | ... | 2147483647
```

最初と最後の値コンストラクタは最小・最大値です。
※これは説明のための例であって、実際にはこのように宣言されていません。

さて、 Haskell で図形を表す方法を考えてみましょう。１つの手段は `Tuple` を使うことです。
円は `(43.1, 55.0, 10.0)` と表すことができ、１つめと２つめのフィールドは円の中心の座標を示しており、３つ目は半径を示しています。

他にも三次元を表現することも可能です。よりよいソリューションは独自の型を作成して図形を表現することです。円もしくは長方形を表現した型は以下のようになります。


```haskell
data Shape = Circle Float Float Float | Rectangle Float Float Float Float
```

上記について考えて見ましょう。
`Circle` の値コンストラクタは３つの `Float` のフィールドを持ちます。値コンストラクタを書くとき、必要に応じていくつかの型を追加でき、それらの型はそれに含まれる値を定義します。

`Rectangle` の値コンストラクタは４つのフィールドを持ち、最初の２つは左上の座標、残りの２つは右上の座標を示しています。

値コンストラクタは実際には最終的にデータ型の値を返す関数です。これらの値コンストラクタの型シグネチャは以下のようになっています。

```haskell
ghci>:t Circle
Circle :: Float -> Float -> Float -> Shape
ghci>:t Rectangle
Rectangle :: Float -> Float -> Float -> Float -> Shape
```

図形を引数とし、面積を計算して返す関数を作ってみます。

```haskell
surface :: Shape -> Float
surface (Circle _ _ r) = pi * r ^ 2
surface (Rectangle x1 y1 x2 y2) = (abs $ x2 - x1) * (abs $ y2 - y1)
```

注目すべきは型の宣言です。`Shape` を引数に取り、`Float` を返すことを示しています。`Circle` は型ではありませんので、`Circle -> Float` というような型定義はできません。`True -> Int` のような型定義が作れないのと同様です。

次に注目すべきはコンストラクタに対してパターンマッチしているところです。`[]` や `False`, `5` のような値に対してもパターンマッチできますがこれらはいくつかフィールドを持っているわけではありません。ここではコンストラクタを書いて、値に名前を付けてバインドしています。`Circle` に関しては値に関心があるので１つ目と２つ目のフィールドに関しては気にしていません。

```haskell
ghci>surface $ Circle 10 20 10
314.15927
ghci>surface $ Re
ghci>surface $ Rectangle 0 0 100 100
10000.0
```

作成した関数が動作することを確認できました。

しかし、`Circle 10 20 5` をプロンプトに打ち込むとエラーになってしまいます。これは Haskell が私達のデータ型を文字列として表示するする方法を知らないからです。

プロンプトに値を表示させようとすると Haskell は値の文字列表現を取得するために `show` 関数を実行します。そのため `Shape` 型を `Show` 型クラスの一部として作成する必要があります。次のように修正します。

```haskell
data Shape = Circle Float Float Float | Rectangle Float Float Float Float deriving (Show)
```

`deriving(Show)` をデータ宣言の末尾に追加すると、Haskell は自動的にその型を `Show` 型クラスの実装を自動導出します。

```haskell
ghci>Circle 10 20 5
Circle 10.0 20.0 5.0
ghci>Rectangle 50 230 60 90
Rectangle 50.0 230.0 60.0 90.0
```

値コンストラクタは関数なので、それらをマッピングして部分適用するような使い方もできます。

```haskell
ghci>map (Circle 10 20) [4,5,6,6]
[Circle 10.0 20.0 4.0,Circle 10.0 20.0 5.0,Circle 10.0 20.0 6.0,Circle 10.0 20.0 6.0]
```

これらのデータ型は更に良くすることができます。二次元空間の座標を定義する中間データ型を作成します。

```haskell
data Point = Point Float Float deriving (Show)
data Shape = Circle Point Float | Rectangle Point Point deriving (Show)
```

`Point` を定義するときに、データ型と値コンストラクタに同じ名前を使用していることに注目してください。これに特別な意味はありませんが、値コンストラクタが１つしかない場合は、型と同じ名前を使用するのが慣習となっています。

さて、これで `Circle` は `Point` と `Float` の２つのフィールドを持つようになりました。各フィールドが何を意味するのかより理解しやすくなりました。`Rectangle` においても同様です。`surface` 関数にもこれらの変更を反映させます。

```haskell
surface :: Shape -> Float
surface (Circle _ r) = pi * r ^ 2
surface (Rectangle (Point x1 y1) (Point x2 y2)) = (abs $ x2 - x1) * (abs $ y2 - y1)
```

変更が必要だったのはパターンマッチしている箇所のみです。`Circle` のパターンマッチでは `Point` のフィールドに該当する箇所を無視しています。`Rectangle` では `Point` を階層化してパターンマッチしています。`Point` そのものを参照したい場合には、[as-patterns](http://zvon.org/other/haskell/Outputsyntax/As-patterns_reference.html) を利用できます。

```haskell
ghci>surface (Rectangle (Point 0 0) (Point 100 100))
10000.0
ghci>surface (Circle (Point 0 0) 24)
1809.5574
```

さて、これら図形の位置を調節する関数を作る場合はどのようになるのでしょうか。図形と、x軸とy軸の移動距離を引数にとり。同じ寸法の新しい形状の図形を返却するようにすればよさそうです。単に別の箇所に位置しているということになります。

```haskell
nudge :: Shape -> Float -> Float -> Shape
nudge (Circle (Point x y) r) a b = Circle (Point (x + a) (y + b)) r
nudge (Rectangle (Point x1 y1) (Point x2 y2)) a b = Rectangle (Point (x1 + a) (y1 + b)) (Point (x2 + a) (y2 + b))
```

非常に簡単です。図形の位置を示す `Point` のフィールドに調整する値を加算するだけです。

```haskell
ghci>nudge (Circle (Point 34 34) 10) 5 10
Circle (Point 39.0 44.0) 10.0
```

もし、`Point` を直接扱いたくなければ、ゼロ座標で図形を生成する補助関数を作って利用することができます。

```haskell
baseCircle :: Float -> Shape
baseCircle r = Circle (Point 0 0) r

baseRect :: Float -> Float -> Shape
baseRect width height = Rectangle (Point 0 0) (Point width height)
```

```haskell
ghci>nudge (baseRect 40 100) 60 23
Rectangle (Point 60.0 23.0) (Point 100.0 123.0)
```

当然ですが、独自のデータ型を独自のモジュールとして export することもできます。そのためにはまず、export する関数と共に型を記述します。値コンストラクタの export は括弧を追加して、その中でカンマで区切って値コンストラクタを指定します。すべての値コンストラクタを export したい場合は括弧内を `..` とします。

これまでに定義したデータ型と関数を export するには以下のようにします。

```haskell
module Shapes   
( Point(..)  
, Shape(..)  
, surface  
, nudge  
, baseCircle  
, baseRect  
) where  
```

`Shape(..)` のようにすることで、`Shape` の全ての値コンストラクタを export します。これはつまり、module を import さえすれば誰でも、`Rectangle`, `Circle` の値コンストラクタを利用して、`Shape` を生成できることを意味しています。これは以下の記述と同じです。

```haskell
Shape (Rectangle, Circle)
```

また、export のステートメントに `Shape` とだけ記述することで値コンストラクタを export しないこともできます。そうすることで利用者は`Shape` の生成を補助関数の `baseCircle`, `baseRect` を使用することに限定できます。`Data.Map` はこのアプローチを使っています。値コンストラクタが export されていないので されていないので `Map.Map [(1,2),(3,4)]` のようなことはできません。`Map.fromList` のような補助関数を使ってマッピングできます。

値コンストラクタは、フィールドをパラメータとして受け取り、結果として何らかの型の値を返す関数に過ぎません。なので export しなければ module を import した人がこれらの関数を使用するのを防ぐことになります。データ型の値コンストラクタを export しないことで、実装を隠し、抽象化を強めることができます。また module を使用するユーザーは、値コンストラクタに対してパターンマッチングを実行できません。

---

# まとめ

* `data` キーワードを使い代数的データ型を独自に定義できる。
* 値コンストラクタは
フィールドをパラメータを引数にとりデータ型を返す関数として定義される。
* 中間データ型を作るとモデルが洗礼され、理解の助けになる。
* module として export することで他の箇所から利用できるようになる。
* 値コンストラクタは export せず、代わりに補助関数を export し利用してもらうことで実装を隠蔽し、抽象化できる。

次回に続きます。