1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Scala 3 の opaque type で型クラスを宣言する際の注意点

Last updated at Posted at 2024-01-03

はじめに

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 で型クラスを宣言する

間違えた実装

最初に考えたのが、MyStringString を ラップしたようなものなので、その情報を使うように以下のように実装しました

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
先ほどの実装だと、MyStringString が同一の型とみなされて無限ループとなり値が返ってこなかったようです。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 のところではまってしまいました。もし参考になるのであれば幸いです。また面倒な実装となっているのでこれよりも良い方法がある場合は是非とも教えてほしいです。

  1. 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

  2. ここに詳しい挙動について説明があります https://github.com/circe/circe/issues/1829#issuecomment-969658521

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?