代数的データ型を利用した抽象化とクラスシステムによる継承との比較

以前、[ReasonMLについての記事](https://qiita.com/oedkty/items/3fe4c68e404689476a58)を書きました。

その時は、ReasonMLにおける**ADT＝Algebraic Data Type（代数的データ型）実装**である[Variant](https://reasonml.github.io/docs/en/variant)の用途がイマイチ分からず、**列挙型のナンカスゴイ版**として扱っていました。

ReasonMLにおけるVariantとは下記のようなもので、RustだとEnumに当たります。

```javascript
type animal =
  | Dog
  | Cat
  | Duck;
```

いわゆる直和っていうもので、色んな型の和として別の型を表現するような感じですかね。

で、これをどう使うかというと、

```javascript
let cry = () => switch(Dog) {
  | Dog => "wan!"
  | Cat => "nyaa!"
  | Duck => "kuwa!!"
}

Js.log(cry()) // ただのconsole.logです。
```

上の例だと、`wan!`って出力されます。

説明のために、この例を出してみたんですが、多分あまり旨味を感じないと思うので、少し複雑な例を出します。

# Amazonの書籍購入

Amazonで書籍を購入する時って、Kindle（電子本）とPaperback（紙の本）を選べるんですね。

で、その二つをモデリングした場合、

book <-> kindle paperback

っていうような`is-a`関係があることがわかります。（kindle is a book. paperback is a book）

（正確には、kindle is a kind of book.かも。まあ良いか。。。）

このとき、bookというのは、kindleとpaperbackの**抽象的な表現**であることがわかります。

つまり、下記のようなグラフの状態です。

![Frame 1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/169861/189d9c0e-4ee5-76a3-c163-f148ed7d312b.png)

（ここら辺についてよく分からない方は、苫米地英人氏による論文をご覧ください。→[「空」を定義する ~現代分析哲学とメタ数理的アプローチ](http://tomabechi.jp/EmptinessJapanese.pdf)。この方は難しいと思われていることを易しく教える天才です。）

じゃあ、こういう関係をクラスシステムではどう表現していたかというと、

```javascript
class Book {
}

class Kindle extends Book {
}

class Paperback extends Book {
}
```
（JavaScript）

というような継承を用います。

で、「電子本には割引が発生する」という仕様があるとします。

その場合の値段計算のロジックを考えてみましょう。

```javascript
class Book {
}

class Kindle extends Book {
  constructor(price, discount) {
    super()

    this.price = price
    this.discount = discount
  }

  calcPrice() {
    return this.price * (1 - this.discount)
  }
}

class Paperback extends Book {
  constructor(price) {
    super()

    this.price = price
  }

  calcPrice() {
    return this.price
  }
}
```

となったとします。

で、この仕様について話し合ったところ、「本の値段計算は、一律で　値段×割引率　にする」と決まったとします。（なんか無理やりだなあ。。。）

今後、本の形態が増えても、同じくロジックで対応したいため、「本」に関する**一般的なロジック**として扱うように修正したとしましょう。

```javascript
class Book {
  constructor(price, discount) {
    this.price = price
    this.discount = discount
  }

  calcPrice() {
    return this.price * (1 - this.discount)
  }
}

class Kindle extends Book {
  constructor(price, discount) {
    super(price, discount) // Bookのコンストラクタの呼び出し
  }
}

class Paperback extends Book {
  constructor(price) {
    super(price, 0) // 紙の本は、割引が発生しないから割引率は「０」
  }
}

const kindle = new Kindle(100, 0.1)

console.log(kindle.calcPrice()) // 90
```

では、これと同じことをVariantで行うとどうなるでしょう。

まず、先ほどの`animal`型は、`Dog`や`Cat`の抽象型として機能していることを思い出してください。

その考え方からいくと、

```javascript
type book =
| Kindle
| Paperback;
```

とできます。また、Variantはコンストラクタを受け取れるので、そこに値段や割引率を設定できるようにしましょう。

```javascript
type book =
| Kindle(int, float) /* int: 値段 float: 割引率 */
| Paperback(int); /* int: 値段 */
```

では、classの代わりにこれらを`Book`モジュールに入れてあげます。

値段計算のロジックはどうなるでしょう。

```javascript
let calcPrice = (x) => switch(x) {
| Kindle(price, discount) => Js.Int.toFloat(price) *. (1.0 -. discount) /* intのfloat変換が必要 */
| Paperback(price) => price
}
```

このように書けます。

では先ほどと同様に計算ロジックを一般化させるために、クラスではなく`Book`モジュールを使いましょう。

`Book`モジュールには、型の定義と共用のロジックを入れ込んでしまいましょう。

```javascript
module Book = {
  type book =
  | Kindle(int, float) /* int: 値段 float: 割引率 */
  | Paperback(int); /* int: 値段 */


  let calcPrice = (x) => {
    let calc = (price, discount) => Js.Int.toFloat(price) *. (1.0 -. discount)

    switch(x) {
  	| Kindle(price, discount) => calc(price, discount)
  	| Paperback(price) => calc(price, 0.0) /* 紙の本は割引なし = 割引率０ */
  	}
  }
}
```

このモジュールを使うように、先ほどの実装を直してみます。

```javascript
Js.log(Book.calcPrice(Book.Kindle(100, 0.1))) /* 90 */
```

というように、表現することができました。

よって、ADTは抽象化の手段として使えるのではないかな、という比較でした。