Matching Lists vs Arrays

Consider the following

val fruitL = List("apples", "oranges", "pears", "mangos")
val fruitA = Array("apples", "oranges", "pears", "mangos")

we can prepend “bananas” using the following construct:

"bananas" :: fruitL 
// List(bananas, apples, oranges, pears, mangos)
"bananas" +: fruitA
 // Array(bananas, apples, oranges, pears, mangos)

We can also match (extract)

val a :: b :: rest = fruitL
//val a: String = apples
//val b: String = oranges
//val rest: List[String] = List(pears, mangos)

But how how can one match (extract) the Array? The following does not work (why?)

val a +: b +: rest = fruitA

You can do something like this

  val Array(_, myFruit, _@_*) = fruitA

to get one specific value out.

1 Like

Right. We can also cite the book, “these sequence patterns are all implemented using extractors in the standard Scala library.” So, my question is not about the sequence patterns, but rather the extractors.

Don’t use arrays for general-purpose programming, they are mutable, invariant, their equals is by reference instead of by value, they don’t have a nice toString, they are not part of the collections hierarchy etc.

The only reason for using Array is for performance-sensitive code and if you know how to properly use them. And, pattern matching will have a horrible performance on an Array since it would need to copy a lot of data; thus no, don’t do this.

1 Like

Although “avoid Array except for performance-sensitive code” is a nice drum beat, it’s still somewhat true that the Java-derived main entry point takes Array[String].

It’s unfortunate that array is a simple concept, with intuitive support in certain languages, but hits a wall in Scala.

TIL there was a SID for Array, which begins:

Arrays have turned out to be one of the trickiest concepts to get right in Scala.

That document includes interesting ancient history about pre-2.8 Array, with compiler support, an era that deserves a special designation, much as we say, “antediluvian”.

I just noticed that I called array a “concept”, then went to read the docs where the SID also calls it a concept. It is not just an artifact or an implementation detail. It has a special status in our mental apparatus.

The overview page is also informative about the current solution, but doesn’t happen to cover destructuring. The API Scaladoc is a compressed version.

Some of the machinery for the construction made visible:

scala> "z" +: Array("a") // print

{
  val rassoc$1: String("z") = "z";
  scala.Predef.refArrayOps[String](scala.Array.apply[String]("a")(scala.reflect.`package`.materializeClassTag[String]())).+:[String](rassoc$1)(scala.reflect.`package`.materializeClassTag[String]())
} // : Array[String]

The annoying part about

scala> val x +: rest = res0
             ^
       error: scrutinee is incompatible with pattern type;
        found   : C with scala.collection.SeqOps[A,CC,C]
        required: Array[String]

scala> val x +: rest = res0: collection.ArrayOps[String]
             ^
       error: scrutinee is incompatible with pattern type;
        found   : C with scala.collection.SeqOps[A,CC,C]
        required: scala.collection.ArrayOps[String]

granting that the Scaladoc for ArrayOps warns

Neither Array nor ArrayOps are proper collection types

is that the best you can do is commit to ArraySeq:

scala> val x +: rest = res0.toSeq
val x: String = z
val rest: Seq[String] = ArraySeq(a)

scala> val x +: rest = res0: Seq[String]
                       ^
       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
val x: String = z
val rest: Seq[String] = ArraySeq(a)

Elsewhere, the balancing act allows me to choose whether I’d like to “transiently” view the array as a seq, but keep my array type.

This would fall in the same ball park:

scala> Array(42) match { case Nil => 1 case _ => 2 }
                              ^
       error: type mismatch;
        found   : collection.immutable.Nil.type
        required: Array[Int]

scala> Seq(42) match { case Nil => 1 case _ => 2 }
val res2: Int = 2

That is, this is a matter of convenience. “I’m comfortable with +: to give me the head and tail. I’m not terribly concerned with performance, but main gave me an Array and also I’m calling an API that also takes an Array. I just want simple code to work simply without any head scratching. If pattern matching can paper over some seams without the wallpaper peeling, that would be great.”

3 Likes