Scala3で弱適合性(= Week Conformance)の削除で問題が発生するコードについて
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)