The contract isn’t broken. The contract is the contract: you can do Object
easily and everything else is hard, so it does the easy thing. That’s the contract!
Do you want to try to do a harder thing? You can write it yourself! You have to call IArray.equals
manually anyway. If you want Java deepEquals, just call it. Every IArray[T]
is just Array[T]
so you can asInstanceOf
it over. You can write a helper function that does it:
extension [T <: AnyRef](a: IArray[T])
def deepEquals[U <: AnyRef](b: IArray[U]) =
java.util.Arrays.deepEquals(a.asInstanceOf[Array[AnyRef]], b.asInstanceOf[Array[AnyRef]])
Now you can deepEquals
it. But wait! What if you want deepEquals
on any type of array? It turns out that you can’t express that in Java so it doesn’t support it–save for System.arrayCopy
which takes Object
instead of Array
because there’s no superclass for a general Array (!!)–so you have to do it manually. And then you have to decide if Array(1, 2, 3)
is or is not equal to Array(1L, 2L, 3L)
. In Scala, 1 == 1L, 2 == 2L, and 3 == 3L, but in Java Array(1, 2, 3).equals(Array(1L, 2L, 3L))
is false, and there is no java.util.Arrays.equals
method that takes Array[Int]
and Array[Long]
. Hence, deepEquals
, unlike Scala collection equals, will return false
if there are embedded Array
s with identical elements but a different primitive type.
But then you realize that Array[String]
and Array[CharSequence]
and Array[AnyRef]
are actually all different class types, but they all return true for isInstanceOf[Array[AnyRef]]
even though Array[AnyRef]
is not a superclass of Array[CharSequence]
etc.! So you can totally go
if xs.isInstanceOf[Array[AnyRef]] then
val ys = xs.asInstanceOf[Array[AnyRef]]
ys(0) = "Uh-oh"
and that will totally work even if it’s an Array[Option[Boolean]]
. And crash later with a ClassCastException when you try to use the Option[Boolean]
that is actually a string.
Around about this time, most people throw up their hands and declare arrays broken by design, and use Scala collections which have sensible policies for all these things.
But if you haven’t thrown up your hands, and you still want to keep at it, you can use asInstanceOf[Array[Char]]
etc. to detect primitive arrays, or you can just accept that you can’t even call Array(1, 2).deepEquals(Array(1L, 2L))
(or, more likely, def foo[X, Y](xs: Array[X], ys: Array[Y]) = xs.deepEquals(ys)
); or you can pattern match out all the primitives for comparisons like that and defer to java.util.Arrays.equals
if the types match, even though it has different behavior than every other collection type, or write your own recursive routine.
But the designers of the Scala library decided, sensibly enough, that this is all a minefield and if you want well-behaved collections, you can use the collections library. Including wrapped Array
s, if that’s what you want–the wrapper smooths out all the wrinkles.
So, wherever you might be tempted to use IArray
, think about whether collection.immutable.ArraySeq
would do instead. Sometimes it doesn’t; ArraySeq
is not guaranteed to hold primitives as primitives; ArraySeq(1, 2, 3)
wraps a primitive Array(1, 2, 3)
(Array[Int]
) but ArraySeq("a", "bc", "def").map(_.length)
is also typed as ArraySeq[Int]
but stores the values as boxed because map
acts generically.
Anyway, the bottom line of all of this is that Array
and IArray
allow high-performance primitive arrays to be easily accessible in Scala as well as Java, but they don’t shield you from all the weirdnesses of primitive arrays. If you want to build your own shields, go for it! You can write extension methods. But the rest of us have just given up and decided that we will use them very intentionally as the weird things that they are.