Dotty で入る Opaque Type Aliases について見ていく

これは Scala Advent Calendar 5日目の記事です。

Dotty で入る新機能 `opaque`（オペーク by Google翻訳）について見ていきます。
https://dotty.epfl.ch/docs/reference/other-new-features/opaques.html

# はじめに

Haskell の `newtype` について同僚と話す機会があり Dotty にも似たような機能が実装されるということを耳にしたので見てみることにしました。

事実、SIP にも Haskell の `newtype` に類似機能であるという言及がありました。
https://docs.scala-lang.org/sips/opaque-types.html

# Haskell の newtype

Haskell の `newtype` を簡潔に説明すると **既存の型から別の型を定義する** ではないでしょうか。

例えば以下のように `Integer` からオリジナルの `MyInteger` を生成します。

```haskell
newtype MyInteger = MyInteger { getInteger :: Integer } deriving (Eq, Show)
```

新たに定義した型は、もとの型とは異なるので同値判定しようとしても当然エラーになります。

```haskell
ghci>a = MyInteger 1
ghci>b = 1
ghci>a == a
True
ghci>b == b
True
ghci>a == b

<interactive>:11:6: error:
    • No instance for (Num MyInteger) arising from a use of ‘b’
    • In the second argument of ‘(==)’, namely ‘b’
      In the expression: a == b
      In an equation for ‘it’: it = a == b
```

このままでは何もできませんが。型クラスのインスタンスを実装してやることで機能拡張が可能です。

```haskell
-- Num を継承した型クラスのインスタンスを実装する
instance Num MyInteger where
  fromInteger = MyInteger x
  x + y       = MyInteger $ getInteger x + getInteger y
  x - y       = MyInteger $ getInteger x - getInteger y
  x * y       = MyInteger $ getInteger x * getInteger y
  abs x       = if getInteger x < 0 then x * (-x) else x
  signum x    = if getInteger x < 0 then -1 else 1

ghci> MyInteger 1 + MyInteger 2
-- MyInteger {getInteger = 3}
```

# Scala の opaque

Scala の `opaque` も触ってみましたが確かに似ているなという印象で、個人的にはさらに柔軟なように感じました。

下記では、`Int` からオリジナルの `MyInt` を新たに定義しています。

```scala
object MyObject {

  opaque type MyInt = Int

  object MyInt {
    def apply(i: Int): MyInt = i
  }

  given MyIntOps: {
    def (x: MyInt) getInt: Int = x.toInt
    def (x: MyInt) + (y: MyInt): MyInt = MyInt(x + y)
    def (x: MyInt) * (y: MyInt): MyInt = MyInt(x *y)
  }
}
```

オブジェクトの実装ではファクトリーメソッドを定義し Given インスタンスからはレシーバーを提供します。

https://dotty.epfl.ch/docs/reference/other-new-features/opaques-details.html#relationship-to-sip-35

> The notion of a companion object for opaque type aliases has been dropped.

ここでの `object MyInt` はコンパニオンオブジェクトという概念とは違うものらしい。なんて呼べば良いかわからないのでこの記事ではタイプオブジェクトと呼ぶことにします。

```scala
scala> import MyObject._

scala> MyInt(1) + MyInt(1)
// MyObject.MyInt = 2

scala> MyInt(1) * MyInt(1)
// MyObject.MyInt = 1

scala> MyInt(10).getInt
// Int = 10
```

当然ながら、新しく定義した型は、もとの型とは異なるため同じ型として扱うことはできません。

```scala
scala> val a = MyInt(1)
val a: MyObject.MyInt = 1

scala> val b = 1
val b: Int = 1

scala> a == b
1 |a == b
  |^^^^^^
  |Values of types MyObject.MyInt and Int cannot be compared with == or !=
```

この挙動は Haskell の `newtype` と似ているように思えます。

# 何が嬉しいのか

### 値クラスのパフォーマンス問題の解決

単純な `Id` や `Password` といった情報は混同を防ぐためにラッパー型を定義することがあると思います。

```scala
case class Id(v: String) extends AnyVal
case class Password(v: String) extends AnyVal
```

`AnyVal` を継承した定義は **値クラス** と呼ばれ、実行時にインライン化してくれるのでボクシングのオーバーヘッドが発生しないという利点があります。

しかしながら、厳密にはメモリ割当てが必要なユースケースが存在するのも既知の事実でした。
https://docs.scala-lang.org/overviews/core/value-classes.html#when-allocation-is-necessary

代表的な例としてはタプルやコレクションに値クラスを入力した場合などです。

```scala
case class Label(v: String) extends AnyVal
val first = Label("first")
val second Label("second")

// メモリ食う
List(first, second)
(first, second)
```

値クラスのパフォーマンスに問題に対する言及についてはこちらの記事も面白かったです。
https://failex.blogspot.com/2017/04/the-high-cost-of-anyval-subclasses.html

これに対し、同様のことを `opaque` やろうとしたときにボクシングのオーバーヘッドが一切発生せず、メモリの消費についても改善されるようです。

### 型エイリアスとして機能しながらも拡張可能である

すでにコードでも紹介したとおり、型エイリアスの宣言に加えてタイプオブジェクトの実装や暗黙的な宣言を通じて拡張メソッドを定義できるというのは利点のように思えます。

これに関しては値クラスの機能と同等価値を提供できているといって良いでしょう。

### 情報隠蔽のしやすさ

値クラスないし、`case class` のデフォルトコンストラクタの隠蔽についてしばしば議論されることがありました。
https://qiita.com/petitviolet/items/b6af2877f64ebe8fe312

`opaque` はタイプオブジェクトにファクトリーメソッドを実装しない限りはデフォルトで生成する手段を提供しないので情報隠蔽のしやすさという点では適しているように思えます。

```scala
scala> object A {
     |   opaque type MyA = Int
     | }
// defined object A

scala> A.MyA
1 |A.MyA
  |^^^^^
  |value MyA is not a member of object A
```

以下のように `case class` の提供に制約を設けられるのはかなり嬉しいポイントだと思います。

```scala
scala> object MyContext {
     |   private case class Inside(v: Int)
     |   opaque type Outside = Inside
     |   object Outside {
     |     def apply(v: Int): Outside= Inside(v)
     |   }
     | }
// defined object MyContext

scala> MyContext.Outside(1)
val res0: MyContext.Outside = Inside(1)

scala> MyContext.Inside(1)
1 |MyContext.Inside(1)
  |^^^^^^^^^^^^^^^^
  |object Inside in object MyContext cannot be accessed as a member of MyContext.type from module class rs$line$4$.
```

### アーティファクトの軽量化

値クラスと比較したときにアーティファクトのサイズが小さくなるということでした。
値クラスの場合はラッパークラスへのボクシングとそれの解除がコストになりますが、それとは対象的に `opaque` には固有のフットプリントがないのでアーティファクトが軽量化されるということでした。

# まとめ

- `opaque` は Haskell の `newtype` の類似機能。
- `opaque` は値クラスの抱えていた課題を改善してくれるすごいやつ。
- 情報隠蔽しやすい（個人的なお気に入り

# 最後に

東京オリンピックイヤーでもある翌年２０２０年に Dotty のリリースが予定されているのは神様のイタズラでしょうか。とても楽しみですね。

今後の動向にも目を光らせていきたいと思います。

# おわり