Use implicit to automatically choose ArraySeq.unsafeWrapArray

Finally we’re updating a larger codebase to Scala 2.13 and 3. It used a lot of arrays for performance and interop reasons and passed them to methods needing Seq and IndexedSeq. This results in a lot of (deserved) deprecation warnings.

Implicit conversions from Array to immutable.IndexedSeq are implemented
by copying; Use the more efficient non-copying ArraySeq.unsafeWrapArray or an
explicit toIndexedSeq call

I looked at them all and most if not all should be rewritten to ArraySeq.unsafeWrapArray(array). I can add an implicit method so that array.toWrapped performs the conversion (by analogy to array.toIndexedSeq) with this code:

object WrappedArray {
  implicit def toWrapped[T: _root_.scala.reflect.ClassTag](array: Array[T]): _root_.scala.collection.immutable.IndexedSeq[T] =
      _root_.scala.collection.immutable.ArraySeq.unsafeWrapArray(array)
}

However, I would much prefer not to edit multiple lines in each file, turning array into array.toWrapped each time, but simply add an import to the top of each file in order to override the implicit toIndexedSeq conversion. Anything I’ve come up with so far either doesn’t see my conversion or results in something like

Note that implicit conversions are not applicable because they are ambiguous:
 both method copyArrayToImmutableIndexedSeq in class LowPriorityImplicits2 of type [T](xs: Array[T]): IndexedSeq[T]
 and method toWrappedIndexedSeq in class TestArray of type [T](array: Array[T])(implicit evidence$1: scala.reflect.ClassTag[T]): IndexedSeq[T]
 are possible conversion functions from Array[Int] to Seq[Int]
    val autoCopySeq1 = processSeq(array) // calls new ArrayOps(xs).toIndexedSeq

Sometimes I’ve added code like

import Predef.{copyArrayToImmutableIndexedSeq => _,_}

to attempt to reduce the ambiguity, but it doesn’t work and I’m hoping to just use a single import anyway.

The implicit conversions apparently have priorities, but so far, my import has not been able to override the Predef version and compilation always fails.

Does anyone know the correct incantation? A program like this should work correctly:

package mypackage

// This should be in a separate file and imported.
object WrappedArray {
  implicit def toWrapped[T: _root_.scala.reflect.ClassTag](array: Array[T]): _root_.scala.collection.immutable.IndexedSeq[T] = {
    println("I was here!")
    _root_.scala.collection.immutable.ArraySeq.unsafeWrapArray(array)
  }
}

object ArrayExample extends App {

  def process(indexedSeq: IndexedSeq[Int]): Unit = println("It was processed.")

  process(Array(1, 2,3, 4, 5))
}

It appears that the trick is to provide an alternative method with the same name as a pre-defined implicit conversion but that doesn’t perform the correct conversion. That somehow rules out its namesake. So, this little test program works with source code unchanged in Scala 2.11, 2.12, 2.13, 3.0, and 3.1

package mypackage.unsafeWrapArray

import mypackage.unsafeWrapArray.WrappedArray._

object UnsafeWrapArrayApp extends App {
  val array = Array(1, 2, 3, 4, 5)

  def process(indexedSeq: IndexedSeq[Int]): Unit = println(indexedSeq)

  process(array)
}

as long as it is backed up by this little file for Scala 2.13, 3.0, and 3.1

package mypackage.unsafeWrapArray

import scala.collection.immutable.ArraySeq
import scala.language.implicitConversions

object WrappedArray {

  // Undo the standard conversion to prevent ambiquity from LowPriorityImplicits2 resulting in error.
  // "Note that implicit conversions are not applicable because they are ambiguous:".
  // See https://stackoverflow.com/questions/15592324/how-can-an-implicit-be-unimported-from-the-scala-repl.
  def copyArrayToImmutableIndexedSeq[T](xs: Array[T]) = ()

  implicit def toIndexedSeq[T](array: Array[T]): IndexedSeq[T] = {
    println("This is my implicit conversion!")
    ArraySeq.unsafeWrapArray(array)
  }
}

or this one for Scala 2.11 and 2.12

package mypackage.unsafeWrapArray

import scala.language.implicitConversions

object WrappedArray {

  // This is to avoid ambiguity from LowPriorityImplicits resulting in error.
  // "Note that implicit conversions are not applicable because they are ambiguous:".
  // See https://stackoverflow.com/questions/15592324/how-can-an-implicit-be-unimported-from-the-scala-repl.
  def genericWrapArray[T](xs: Array[T]): Unit = ()
  def wrapRefArray[T <: AnyRef](xs: Array[T]): Unit = ()
  def wrapIntArray(xs: Array[Int]): Unit = ()
  def wrapDoubleArray(xs: Array[Double]): Unit = ()
  def wrapLongArray(xs: Array[Long]): Unit = ()
  def wrapFloatArray(xs: Array[Float]): Unit = ()
  def wrapCharArray(xs: Array[Char]): Unit = ()
  def wrapByteArray(xs: Array[Byte]): Unit = ()
  def wrapShortArray(xs: Array[Short]): Unit = ()
  def wrapBooleanArray(xs: Array[Boolean]): Unit = ()
  def wrapUnitArray(xs: Array[Unit]): Unit = ()

  // If the standard conversion suffices, all of these methods can be commented out.
  implicit def toIndexedSeq[T](array: Array[T]): IndexedSeq[T] = {
    println("This is my implicit conversion!")
    Predef.genericWrapArray(array)
  }
}

The name trick works because in Scala 2 the implicit must be “available as a simple name”. Your conversion could have the shadowing name directly.

Otherwise, without shadowing, the most specific candidate is chosen, as for overloaded methods.

If you only need the the conversion for limited types, then it’s sufficient to supply a specialized conversion:

  implicit def `special wrapper`(array: Array[Int]): _root_.scala.collection.immutable.IndexedSeq[Int] =
    _root_.scala.collection.immutable.ArraySeq.unsafeWrapArray(array)

Thanks for the tips! In order to avoid ambiguities in further code that is being updated, I’ve also needed to shadow genericWrapArray and genericArrayOps somehow. I don’t think that’s a good idea, because I may be creating an extra object all the time, but I haven’t looked into it closely yet.