Search code examples
javascalacollectionscompatibilitymatcher

`::` head and tail deconstruction of a java collection in scala


I am trying to write a "deconstructor" for java collections in scala.

With most scala collections, I can use the :: case class to deconstruct the collection in head/tail:

scala> val (a :: b) = Seq(1,2)
a: Int = 1
b: List[Int] = List(2)

scala> val (a :: b :: Nil) = Seq(1,2)
a: Int = 1
b: Int = 2

And even more complicated cases (e.g. summing the two first elements of the inside lists):

scala> val m = Map("a" -> Seq(1,2,3,4), "b" -> Seq(2,3,4,5))
scala> m.collect { case (k, a :: b :: _) => k ->(a+b)}
res5: scala.collection.immutable.Map[java.lang.String,Int] = Map(a -> 3, b -> 5)

but I can't work out how to have this work for java collections without extraneous code:

Let's say that I get from an external library something like this:

m: java.util.Map[java.lang.String,java.util.List[Int]] = {a=[1, 2, 3, 4], b=[2, 3, 4, 5]}

With scala<=>java collection conversions, I can convert the map to a scala map, and work on the inside lists, which are still java ones:

m.asScala.collect { case (k, jl) => jl.asScala.toList match { case (a :: b :: _) => k->(a+b) } }

or

m.asScala.map{
     case (k, v) => k -> v.asScala.toList
   }.collect { 
     case (k, a :: b :: _) => k ->(a+b)
   }

I have found the unapplySeq matcher trick, but this only works when I know the size of the collection:

object JavaCollection {
  import scala.collection.JavaConverters._
  def unapplySeq[T](array: java.util.Collection[T]): Option[Seq[T]] = Option(array).map(_.asScala.toIndexedSeq)
}

 m.asScala.collect { case (k, JavaCollection(a,b,c,d)) => k ->(a+b)}

How do I get the :: deconstruction to work directly on a Java typed collection without going through the explicit conversion? I have tried building my own case class ::[T](head:T, tail: java.util.Collection[T]) class, but that doesn't seem to be enough.


Solution

  • Using your JavaCollection extractor, here is how you can sum the first two elements without knowing the actual length of the collection:

    scala> val m = Map("a" -> Seq(1,2,3,4,5).asJava, "b" -> Seq(1,2).asJava).asJava
    m: java.util.Map[java.lang.String,java.util.List[Int]] = {a=[1, 2, 3, 4, 5], b=[1, 2]}
    
    scala> m.asScala.collect { case (k, JavaCollection(a, b, rest @ _*)) => k -> (a + b) }
    res3: scala.collection.mutable.Map[java.lang.String,Int] = Map(a -> 3, b -> 3)
    
    scala>