おてがる単位型パターン　〜不正な単位演算はコンパイルエラーにしよう〜

# はじめに :warning:

- 某所Scalaミートアップの発表資料です。
- 用途に適した単位型を、自力で小さく実装することが目的です。
- 実装した言語はScalaですが、各テクニックは他の言語でも応用が利くと思います。

---

# バグを探せ :bug:

```scala
val imp: Int    = 400
val yen: Int    = 30
val cpm: Double = yen / imp
```

広告が400回閲覧されたので、30円支払いました。
単価のCPMはいくらでしょう？

---

# 単位演算は罠だらけ :boom: 

```scala
val imp: Int    = 500
val yen: Int    = 30
val cpm: Double = yen.toDouble / imp.toDouble * 1000
```

- 閲覧（viewable impression）数は、掲載（impression）数と異なる。
- `Int`の割り算は切り捨てなので、割る前に`Double`に変換する。
- CPMは、1000掲載（impression）単位の価格です。

---

# バグを防ぐために :eyes: 

- テストすれば充分……？
  - テストも同じ勘違いをしちゃう問題。。。
- 単位演算をモジュール化すれば、そこだけ気をつければいい……？
  - `def calcCpm(imp: Int, yen: Int): Double`
  - うっかり異なる単位の値を、引数に渡してしまう。。。
  - せっかく作った関数を使いわすれてしまう。。。
- 型に単位情報を埋めこめば、＼型システム／が解決してくれるのでは！

---

# 値クラス＆幽霊型 :ghost: 

```scala
case class UnitBase[A, Tag](value: A) extends AnyVal
```

- 型`A`の`value`を`case class UnitBase`でラップ。
  - 実行時のオーバーヘッドは、値クラス化`extends AnyVal`で回避。
- 実際には使わない幽霊型`Tag`により、同じ型`A`に対して異なるラッパークラスを定義できる。

詳しくは、「[Refactoring in Scala](http://gakuzzzz.github.io/slides/refactoring_in_scala/)」お勧め。

---

# 値クラス＆幽霊型 :ghost:

```scala
def calcCpm(imp: Imp, yen: Yen): Cpm =
  Cpm(yen.value.toDouble / imp.value.toDouble * 1000)
```

- 単位ごとに次のような定義をしていく。

```scala
trait CpmTag
type Cpm = UnitBase[Double, CpmTag]
object Cpm {
  def apply(value: Double) = new Cpm(value)
}
```

- 単位を型で明記できるようになった。
  - うっかり異なる単位の変数を引数に渡すと、コンパイルエラー。
- 実際の演算は面倒になった。。。
  - `case class UnitBase`にメソッドを実装すればいいのでは！

---

# 欲しい演算とは :thought_balloon:

たとえば広告系（アドテク）だと、次のような感じ。

- 同じ型の足し算＆引き算＆大小比較は、元の数値型そのままでいい。
- 掛け算＆割り算は、単位の組み合わせを制限したい。
  - クリック率にコンバージョン数を掛けているコードは、おそらくバグ。
- 掛け算＆割り算は、固定の係数を適用したい。
  - CPMの`1000`とか。

---

# 数値型を統べるもの :crown: 

Q. `Int`や`Double`の演算って、どうやって共通化すればいいの？　スーパークラス無いよね？

A. `Numeric`型クラスとかあるよ！

```scala
trait Numeric[T] extends Ordering[T] {
  def plus(x: T, y: T): T
  def minus(x: T, y: T): T
  def times(x: T, y: T): T
```

```scala
trait Integral[T] extends Numeric[T] {
```

```scala
trait IntIsIntegral extends Integral[Int] {
  def plus(x: Int, y: Int): Int = x + y
  def minus(x: Int, y: Int): Int = x - y
  def times(x: Int, y: Int): Int = x * y
```

```scala
implicit object IntIsIntegral extends IntIsIntegral with Ordering.IntOrdering
```

---

# 型クラスがいっぱい :dancers:

- `Ordering`：大小比較ができる。
- `Numeric`：足し算＆引き算＆掛け算ができる。
- `Fractional`：割り算もできる。
- 掛け算＆割り算で、単位の組み合わせを制限し係数を適用する型クラスは、自力で実装する。

型クラスについては、「[主要な型クラスの紹介](https://gist.github.com/gakuzzzz/8d497609012863b3ea50)」お勧め。

---

# 実装してみた :memo:

- 50行程度の小さな実装です。
- コードの大部分が型の制御。

```scala
case class UnitNum[A, Tag](value: A) extends AnyVal {
  def +(x: UnitNum[A, Tag])(implicit op: Numeric[A]) =
    UnitNum[A, Tag](op.plus(value, x.value))

  def -(x: UnitNum[A, Tag])(implicit op: Numeric[A]) =
    UnitNum[A, Tag](op.minus(value, x.value))

  def *[Tag1, Tag2](x: UnitNum[A, Tag1])(implicit op: Numeric[A], mul: UnitNum.Mul[A, Tag, Tag1, Tag2]) =
    UnitNum[A, Tag2](op.times(op.times(value, x.value), mul.factor))

  def /[Tag1, Tag2](x: UnitNum[A, Tag1])(implicit op: Fractional[A], mul: UnitNum.Mul[A, Tag1, Tag2, Tag]) =
    UnitNum[A, Tag2](op.div(op.div(value, x.value), mul.factor))

  def map[B](f: A => B) =
    UnitNum[B, Tag](f(value))

  def unary_-(implicit op: Numeric[A]) =
    map(op.negate)

  def abs(implicit op: Numeric[A]) =
    map(op.abs)

  def mapInt(implicit op: Numeric[A]) =
    map(op.toInt)

  def mapLong(implicit op: Numeric[A]) =
    map(op.toLong)

  def mapFloat(implicit op: Numeric[A]) =
    map(op.toFloat)

  def mapDouble(implicit op: Numeric[A]) =
    map(op.toDouble)
}

object UnitNum {
  import scala.language.implicitConversions

  implicit def ordering[A: Ordering, Tag]: Ordering[UnitNum[A, Tag]] =
    Ordering.by(_.value)

  implicit def ordered[A: Ordering, Tag](x: UnitNum[A, Tag]): Ordered[UnitNum[A, Tag]] =
    Ordered.orderingToOrdered(x)(ordering[A, Tag])

  case class Mul[A, Tag1, Tag2, Tag3](factor: A) extends AnyVal

  object Mul {
    implicit def commutative[A, Tag1, Tag2, Tag3](implicit mul: Mul[A, Tag1, Tag2, Tag3]) =
      Mul[A, Tag2, Tag1, Tag3](mul.factor)
  }
}
```

---

# 使ってみた :confetti_ball:

```scala
val imp: Vimp = Vimp(400)
val yen: Yen  = Yen(30)
val cpm: Cpm  = yen / imp // コンパイルエラー！！！
```

- 単位の扱い間違えているとコンパイル通らない。
  - `Yen`を`Vimp`で割る演算の結果は、`Cpm`でない。
  - `Yen`を`Imp`で割る時は、実体が`Double`でないといけない。

---

# 使ってみた :confetti_ball:

```scala
val imp: Imp  = Imp(500)
val yen: Yen  = Yen(30)
val cpm: Cpm  = yen.mapDouble / imp.mapDouble
assert(cpm == Cpm(60.0))
```

- バグ潰せばコンパイル通る。
- 係数`1000`は自動で掛けてくれる。

---

# ちょいテク :sunglasses:

```scala
implicit val mulImpCpmYen = UnitNum.Mul[Double, ImpTag, CpmTag, YenTag](0.001)
```

この定義一つで、実体がDoubleならば、次の四つの演算ができるようにしています。

- `Imp` * `Cpm` / 1000 = `Yen`
- `Cpm` * `Imp` / 1000 = `Yen`
- `Yen` / `Imp` * 1000 = `Cpm`
- `Yen` / `Cpm` * 1000 = `Imp`


```scala
def *[Tag1, Tag2](x: UnitNum[A, Tag1])(implicit op: Numeric[A], mul: UnitNum.Mul[A, Tag, Tag1, Tag2]) =
  UnitNum[A, Tag2](op.times(op.times(value, x.value), mul.factor))

def /[Tag1, Tag2](x: UnitNum[A, Tag1])(implicit op: Fractional[A], mul: UnitNum.Mul[A, Tag1, Tag2, Tag]) =
  UnitNum[A, Tag2](op.div(op.div(value, x.value), mul.factor))
```

```scala
object Mul {
  implicit def commutative[A, Tag1, Tag2, Tag3](implicit mul: Mul[A, Tag1, Tag2, Tag3]) =
    Mul[A, Tag2, Tag1, Tag3](mul.factor)
  }
}
```

---

# まとめ :white_flower:

- いくつかの型テクニックを使って、良い感じの単位型を実装することができたよ。
- この実装は、単位演算の要件に応じて細かくカスタマイズすることができるよ。

＼さすがScala／

---

# 他の単位型パターン：物理 :dash:

- メートル毎秒（m/s）を秒（second）で割ったら、メートル毎秒毎秒（m/s^2）になってほしい。
  - `Map("meter" -> 1, "second" -> -2)`という情報を、型に埋めこめればよい。
  - 「[コンパイル時に型レベルで整数を四則演算してみた](http://qiita.com/piyo7/items/1216ad44cae2b2aacece)」みたいな依存型の実現が必要……。
- 「ミリ」や「キロ」などの接頭辞もサポートしたい。
- Scalaのライブラリでは、[KarolS / units](https://github.com/KarolS/units) が個人的な理想に近い。
- C++のライブラリでは、[Boost.Units](http://boostjp.github.io/tips/units.html)が色々と凄い。

---

# 他の単位型パターン：通貨 :chart: 

- 円とか、ドルとか。
- 為替レートが市場依存＆動的なので、為替処理を型で解決するのは危なそう。

---

# 実行環境 :house:

```bash
$ scala -version
Scala code runner version 2.11.8 -- Copyright 2002-2016, LAMP/EPFL
```