Eq型クラス　〜型クラスで見るPureScript〜

<!-- # Eq型クラス -->

※この記事は「[型クラスで見るPureSciript](https://qiita.com/u4da3/items/c1692f7c6e9a53d2d3b5)」の連載記事です。

## 等しいか等しくないか
数学でもプログラミングでも、２つの値が__等しいか等しくないか__を区別できることは、最も基本的な概念です。これを区別できなければ、世の中のほとんどの論理が成り立ちません。そこで、まずは__等しいか等しくないか__を区別するための__イコール演算__を定義しましょう。

その前に、__イコール演算__が満たすべき条件を思い出しておきましょう。みなさんも中学生の時に習ったと思いますが、忘れている人も多いと思うので復習しておきましょう。

右辺の値と左辺の値が等しいことを表す__等号演算子__`==`は次の性質を満たす必要があります。

1. 反射律：$a==a$ 
<br>「`a`は`a`自身と等しい」
1. 対称律：$a==b\;\Rightarrow\;b==a$
<br>「`a`と`b`が等しければ、`b`と`a`も等しい」
1. 推移律：$a==b\;\text{and}\;b==c\;\Rightarrow\;a==c$
<br>「`a`と`b`が等しく、且つ、`b`と`c`が等しければ、`a`と`c`も等しい」

難しいことを言っているように聞こえるかもしれませんが、よく考えると当たり前のことしか言っていません。数学というものは、普段無意識に理解しているような当たり前のことを、小難しい言葉を使って言い直す捻くれ者なのです。

当たり前の性質ではありますが、自分で__イコール演算を__実装する際には、これらの性質を満たさなくなるバグを作らないように、気をつける必要があります。

＊この連載記事では、できる限り、演算子の記号を、通常数学で用いられる記号ではなく、PureScriptのプログラムコード内で使われる記号に一致するように記述することにします。

## Eq型クラス
それではEq型クラスについて、見ていきましょう。Eq型クラスは`Data.Eq`モジュールで次のように定義されています。

```haskell
class Eq a where
  eq :: a -> a -> Boolean
```

つまり「Eq型クラスには、データ型`a`の引数を２つ取り、Boolean型の値を返す関数`eq`が定義されている」ということです。この関数`eq`が、先ほどから何度も言っている__イコール演算__を定義しています。

言葉で説明してもよく分からなくなるだけなので、具体例を見て見ましょう。

## Eq型クラスの例１　座標点を表すデータ型
２次元の座標点を表すデータ型`Point`を次のように定義します。

```haskell
data Point = Point Number Number
```

テスト時に便利なようにShow型クラスも実装しておきましょう。

```haskell
instance showPoint :: Show Point where
  show (Point x y) = "Point (" <> show x <> ", " <> show y <> ")"
```

それでは、このPointデータ型に対してEq型クラスを実装して見ましょう。

２つの座標点 $P=(p_x,p_y)$ と $Q=(q_x,q_y)$ が等しいということは、$P$ と $Q$ の $x$座標と $y$座標が、それぞれ等しいということです。つまり $p_x==q_x\;\text{and}\;p_y==q_y$ ということです。

このことを考慮すると、Eq型クラスの実装は次のようになります。

```haskell
instance eqPoint :: Eq Point where
  eq (Point x1 y1) (Point x2 y2) = x1 == x2 && y1 == y2
```

実装できたらPSCIで軽くテストして見ましょう。

```haskell
> a = Point 1.0 2.0
> b = Point 2.0 3.0
> c = Point 1.0 2.0
> eq a b
false
> eq a c
true
```

## Eq型クラスの関数
### notEq関数

```haskell
notEq :: forall a. Eq a => a -> a -> Boolean
```

notEq関数は、その名の通りEq関数の反対です。２つの値が等しい時に`false`を返却し、等しくない時に`true`を返却します。

eq関数が定義されていれば、notEq関数の定義は一意に定まるので、Eq型クラスに属するデータ型に対してであれば、わざわざnotEq関数を定義しなくても実行可能です。これが型クラスを使うメリットで、限られた幾つかの性質を満たしさえすれば、関連する関数の定義が派生的に定まってしまいます。

## Eq型クラスの例２　長さを表すデータ型
それではnotEq関数を試して見ましょう。先ほどのPointデータ型を使っても良いのですが、ここでは新しいデータ型を定義してみましょう。

```haskell
data Length = Inch Number | CentiMetre Number
```

Lengthデータ型は長さを表すデータ型です。長さの単位には色々ありますが、ここではインチとセンチメートルから選択可能にしておきましょう。手始めにShow型クラスを実装するとこんな感じでしょうか。

```haskell
instance showLength :: Show Length where
  show (Inch l)       = show l <> " in"
  show (CentiMetre l) = show l <> " cm"
```

次にEq型クラスを実装して見ましょう。Lengthデータ型は２種類の単位を選ぶことができるので、異なる単位同士を比較するには単位換算する必要があります。1インチは25.4ミリメートルらしいので、Eq型クラスの実装は次のようになります。

```haskell
instance eqLength :: Eq Length where
  eq (Inch k)       (Inch l)       = k == l
  eq (CentiMetre k) (CentiMetre l) = k == l
  eq (Inch k)       (CentiMetre l) = k * 2.54 == l
  eq (CentiMetre k) (Inch l)       = k == l * 2.54
```

実装できたらPSCIで軽くテストして見ましょう。

```haskell
> j = CentiMetre 1.0
> k = CentiMetre 2.54
> l = Inch 1.0
> notEq j l
true
> notEq k l
false
```

## Eq型クラスの演算子
### (==)演算子
```haskell
infix 4 eq as ==
```

(==)演算子はeq関数の演算子版です。`a == b`のように記述すると`eq a b`と解釈されて、eq関数が実行されます。

### (/=)演算子
```haskell
infix 4 notEq as /=
```

(/=)演算子はnotEq関数の演算子版です。数学記号の「$\neq$」をイメージしているようです。これも同じように`a /= b`と記述すると`notEq a b`が実行されます。



## Eq型クラスの例３　角度を表すデータ型
それでは、もう１つ新しいデータ型を作って試して見ましょう。

```haskell
data Degree = Degree Int

instance showDegree :: Show Degree where
  show (Degree n) = "Degree " <> show n
```

Degreeデータ型は角度を表すデータ型です。ここでは簡単のため、角度の最小単位を1°としておきます。このDegreeデータ型にEq型クラスを実装して見ましょう。

ところで、角度というものはクルッと回って一周すると同じところに戻ってきます。つまり、ぴったり360°の差がある角度は等しいということができます。正確には360°の整数倍の差であれば同一視できます。すると、Eq型クラスの実装は次のようになります。

```haskell
instance eqDegree :: Eq Degree where
  eq (Degree n) (Degree m) = 0 == mod (n - m) 360
```

早速、試して見ましょう。

```haskell
> Degree 90 == Degree 90
true
> Degree (-90) == Degree 90
false
> Degree (-90) == Degree 270
true
```

## みんな違って、みんな楽しい

これで少しはEq型クラスの使い方が分かったでしょうか。同等の機能は他のプログラミング言語にもあるので、簡単だよって方も多いでしょう。

これから先、様々な型クラスについて見ていくことになります。少しずつ書き上げていくので、初めのうちは簡単な方クラスにお付き合いください。それでは、また[次回](https://qiita.com/u4da3/items/653a03ae87c9d34ad241)。

## 演習問題
重さを表すWeightデータ型を定義し、Eq型クラスを実装してみてください。単位にはグラムとオンスを選択可能とし、$1\text{g}=28.35\text{oz}$として実装してみてください。

## 参考文献
Eq型クラスについては下記を参考にしました。

- https://pursuit.purescript.org/packages/purescript-prelude/3.1.0/docs/Data.Eq
- https://github.com/purescript/purescript-prelude/blob/v3.1.0/src/Data/Eq.purs

また、イコール演算については下記を参考にしました。

- https://ja.wikipedia.org/wiki/%e5%90%8c%e5%80%a4