はじめに
Scala 3 で opaque type
という Haskell の newtype
に似た機能が実装されました。
https://dotty.epfl.ch/docs/reference/other-new-features/opaques.html
opaque type
で型クラスを宣言する時にハマったので、その備忘録として残しておきます。
Scala の version は 3.3.1 現在のものとします。
背景
以下の型クラスを定義します
trait Eq[A] {
def eqv(x: A, y: A): Boolean
}
この型クラスを String で宣言します
given Eq[String] with {
override def eqv(x: String, y: String) = x == y
}
このような状態で String の opaque type を定義します。
object MyStringWrapper:
opaque type MyString = String
object MyString:
def apply(value: String); MyString = value
MyString は MyStringWrapper
の外だと String と違うため Eq のインスタンスとはなっていないです。実際に試してみるとコンパイルエラーとなるのがわかります。
summon[Eq[String]].eqv("a", "b") // こっちは OK
import MyStringWrapper.MyString
summon[Eq[MyString]].eqv(MyString("a"), MyString("b")) // MyString は Eq のインスタンスではないのでコンパイルエラー
MyStringWrapper に対して型クラスを宣言していきます。
Opaque Type で型クラスを宣言する
間違えた実装
最初に考えたのが、MyString
は String
を ラップしたようなものなので、その情報を使うように以下のように実装しました
object MyStringWrapper:
opaque type MyString = String
object MyString:
def apply(value: String); MyString = value
given Eq[MyString] with {
override def eqv(x: MyString, y: MyString) = summon[Eq[String]].eqv(x, y)
}
この実装はコンパイルエラーにならず一見良さそうに思いますが実際に実行してみると、値が返ってこなくなります。
import MyStringWrapper.MyString
summon[Eq[MyString]].eqv(MyString("a"), MyString("b")) // コンパイルエラーとならないが実行すると値が返ってこない
何が行けなかったのか
opaque type は同一 scope 内だと、type alias とみなされます。1
先ほどの実装だと、MyString
と String
が同一の型とみなされて無限ループとなり値が返ってこなかったようです。2
別のスコープで実装するようにする
object MyStringWrapper:
opaque type MyString = String
object MyString:
def apply(value: String): MyString = value
extension (x: MyString) def value: String = x
object GivenMyString:
import MyStringWrapper.MyString
import MyStringWrapper.MyString.value
given Eq[MyString] with {
override def eqv(x: MyString, y: MyString): Boolean =
summon[Eq[String]].eqv(x.value, y.value)
}
MyString
の スコープ外から、定義した時別の型として扱われるため、変換用のメソッド value
を定義しています。実行すると値が返るようになります。
import MyStringWrapper.MyString
summon[Eq[MyString]].eqv(MyString("a"), MyString("b")) // false となる
まとめ
Play framework も Scala 3 が対応されたことにより Scala 3 を使う機会がこれから増えるようになると思い Scala 3 を入門してみましたが、opaque type のところではまってしまいました。もし参考になるのであれば幸いです。また面倒な実装となっているのでこれよりも良い方法がある場合は是非とも教えてほしいです。
-
within the scope, it is treated as a type alias, but this is opaque to the outside world where, in consequence https://dotty.epfl.ch/docs/reference/other-new-features/opaques.html ↩
-
ここに詳しい挙動について説明があります https://github.com/circe/circe/issues/1829#issuecomment-969658521 ↩