Magnolia Tech

いつもコードのことばかり考えている人のために。

Scala 2.13からArrayからIndexedSeqへの暗黙の変換が非推奨となる

いよいよ正式リリースが近づいてきたScala 2.13ですが、既存のコードをリリース候補版のScala 2.13でコンパイルすると以下のようなメッセージを見かけるようになりました。

scala> def makeSeq(a: Array[Int]): Seq[Int] = if (a == null) Nil else a
                                                                      ^
       warning: method copyArrayToImmutableIndexedSeq in class LowPriorityImplicits2 is deprecated (since 2.13.0): Implicit conversions from Array to immutable.IndexedSeq are implemented by copying; Use the more efficient non-copying ArraySeq.unsafeWrapArray or an explicit toIndexedSeq call
makeSeq: (a: Array[Int])Seq[Int]

copyArrayToImmutableIndexedSeqなんてメソッド、呼び出していたっけ?LowPriorityImplicits2っていうクラスって何だっけ?そもそも、このメッセージは何を意味しているのか?…調べた結果をまとめました。


まずはScala 2.13へのマイグレーションガイドをチェックします。

https://confadmin.trifork.com/dl/2018/GOTO_Berlin/Migrating_to_Scala_2.13.pdf

20ページに関連する記載が有ります。

Wrapped Java varargs pretend the array is immutable • Using the new s.c.i.ArraySeq • You can do the same with ArraySeq.unsafeWrapArray • A deprecated implicit conversion makes a copy of the array

Scala 2.12までは、ArrayをSeqへ渡すと、scala.collection.mutable.WrappedArrayに変換されていました。これはScala 2.12のscala.Predefで定義されているImplicit Conversionの挙動です。

scala> val r: Seq[Int] = Array(1, 2)
r: Seq[Int] = WrappedArray(1, 2)

Scala 2.13ではSeqがImmutable化されたことを受けて、scala.collection.immutable.ArraySeqに変換されます。

scala> val r: Seq[Int] = Array(1, 2)
                              ^
       warning: method copyArrayToImmutableIndexedSeq in class LowPriorityImplicits2 is deprecated (since 2.13.0): Implicit conversions from Array to immutable.IndexedSeq are implemented by copying; Use the more efficient non-copying ArraySeq.unsafeWrapArray or an explicit toIndexedSeq call
r: Seq[Int] = ArraySeq(1, 2)

これはScala 2.13のscala.Predefで定義が追加された以下のコードによるものです。

private[scala] abstract class LowPriorityImplicits2 {
  @deprecated("Implicit conversions from Array to immutable.IndexedSeq are implemented by copying; Use the more efficient non-copying ArraySeq.unsafeWrapArray or an explicit toIndexedSeq call", "2.13.0")
  implicit def copyArrayToImmutableIndexedSeq[T](xs: Array[T]): IndexedSeq[T] =
    if (xs eq null) null
    else new ArrayOps(xs).toIndexedSeq
}

LowPriorityImplicits2copyArrayToImmutableIndexedSeqが出てきました。暗黙の変換によりコピーが発生するので、toIndexedSeqでコピーして変換するか、それではパフォーマンス的に問題になる場合はArraySeq.unsafeWrapArrayを使ってコピー無しにimmutable化するか明示的に示しましょう、ということですね「ArraySeq.unsafeWrapArray」って何がunsafeなの?という話は、また別のエントリを書きます)。

確かに、警告が出なくなります。

scala> val r1: Seq[Int] = Array(1, 2).toIndexedSeq
r1: Seq[Int] = ArraySeq(1, 2)

scala> val r2: Seq[Int] = scala.collection.immutable.ArraySeq.unsafeWrapArray(Array(1, 2))
r2: Seq[Int] = ArraySeq(1, 2)

しかし、元々LowPriorityImplicits2というクラスも、copyArrayToImmutableIndexedSeqというメソッドも、Scala 2.12まではなかったし、Scala 2.13でも明示的に使うものではありません(implicit conversionですしね)。

定義された瞬間に非推奨になってしまうクラスと、メソッド…しかもユーザーコード上には出てきません…「実は、最初からそんなメソッドなんか無かったんですよ…」って言うと何だか押井守っぽいですね。

というわけで上記のメッセージが出たら、明示的に変換方法を指定しましょう、という話でした。