takafumi blog

日々の勉強メモ

Scala3で弱適合性(= Week Conformance)の削除で問題が発生するコードについて

環境   Scala 2.13.8  Scala 3.1.1

scala3で削除される機能の一つに「弱適合性(= Week Conformance)」というのがある。 docs.scala-lang.org

しかし説明を読み、コードを書いてみても実際に何が問題になるのかが最初よく分からなかった。 ここではscala2からscala3への移行、もしくはそれを前提としてscala2のコードを書くとき、で問題が発生しそうなパターンを書き残しておく。

そもそも弱適合性とは?

弱適合性についてをそもそも理解したい場合はscala2のREFLECTION シンボル、構文木、型#弱適合性関係 を参照。
削除されるものなので細かくは説明は書かない。

問題と解決方法

結論

先に結論を書くと、アプリケーションのレベルであれば、変数や関数に返り型を記述してあれば問題は発生しない

実際のコード

scala2とscala3で同じように書いて、scala3で問題が出るのは以下のようなコードになる。

// scala2
val d = if (true) 1f else 1d
val d2: Double = d

 

// scala3
val d = if (true) 1f else 1d
val d2: Double = d // Error
[error] -- [E007] Type Mismatch Error: /path/to/Main.scala:4:21
[error] 4 |    val d2: Double = d // Error
[error]   |                     ^
[error]   |                     Found:    (d : AnyVal)
[error]   |                     Required: Double

上記のような場合は Floatコンパイル時に自動変換されず AnyVal として扱われるためエラーとなる。

しかしscala3のドキュメントに

Therefore, Scala 3 drops the general notion of weak conformance, and instead keeps one rule: Int literals are adapted to other numeric types if necessary.

とある通り Int の場合は Double へ自動変換されるためcompile可能である。

// scala3
val d = if (true) 1 else 1d // from 1f to 1:Int
val d2: Double = d // OK 

また、次のように関数に返り型が明示されている場合は自動変換され、コンパイルを通過する。
これは Float から Double のように精度が下がらない型変換は暗黙に定義されるので許容される https://dotty.epfl.ch/api/scala/Float$.html#float2double-72d

// scala3
val d: Double = if (true) 1f else 1d // <- Add return type `Double`
val d2: Double = d // OK 

つまり複数の数値型が一つの式の中に存在した時、明示的に型指定をしている場合は、暗黙で変換される。
(ただし高精度から低い精度への変換は定義されていないためエラーとなる)
しかし明示的に型を指定しない場合は、 Int 以外は AnyVal として扱われてしまい、その後の特定の数値型として呼び出すと AnyVal から数値型への変換は暗黙変換が定義されていないためエラーになる。

結論

つまるところ、返り型を常に書いておけば基本的にこの機能削除による影響は受けないと思われる。
またそれ以外の場合も AnyVal からの変換が不可能であるというエラーがcompile時に発生するため、修正せずに妙な動きをするという可能性は低い。

・・・のではないかと考えられる。

余談

scala3ドキュメントに書いてる List の例も、返り型をきちんと書いておけば問題なくcompileできる。

NG

// scala3

def main(args: Array[String]): Unit =
  val ls: List[Double] = foo()

def foo() =  // NG: No return type
  val n: Int = 3
  val c: Char = 'X'
  val d: Double = math.sqrt(3.0)
  List(n, c, d)
[error] -- [E007] Type Mismatch Error: /path/to/Main.scala:3:30
[error] 3 |    val ls: List[Double] = foo()
[error]   |                           ^^^^^
[error]   |                           Found:    List[AnyVal]
[error]   |                           Required: List[Double]

OK

// scala3

def main(args: Array[String]): Unit =
  val ls: List[Double] = foo()

def foo(): List[Double] = // OK
  val n: Int = 3
  val c: Char = 'X'
  val d: Double = math.sqrt(3.0)
  List(n, c, d)