`::` деконструкция на главата и опашката на java колекция в scala

Опитвам се да напиша "деконструктор" за java колекции в scala.

С повечето scala колекции мога да използвам класа case :: за деконструиране на колекцията в 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

И още по-сложни случаи (напр. сумиране на първите два елемента от вътрешните списъци):

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)

но не мога да разбера как това да работи за java колекции без външен код:

Да кажем, че получавам от външна библиотека нещо подобно:

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

Със scala‹=>преобразуване на колекция от java мога да конвертирам картата в scala карта и да работя върху вътрешните списъци, които все още са java:

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)
   }

Намерих трика за съпоставяне unapplySeq, но той работи само когато знам размера на колекцията:

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)}

Как да накарам деконструкцията :: да работи директно върху въведена колекция от Java, без да преминава през изрично преобразуване? Опитах се да създам свой собствен клас case class ::[T](head:T, tail: java.util.Collection[T]), но това не изглежда достатъчно.


person Mortimer    schedule 09.09.2013    source източник


Отговори (1)


Използвайки вашия JavaCollection екстрактор, ето как можете да сумирате първите два елемента, без да знаете действителната дължина на колекцията:

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>
person Ryan LeCompte    schedule 09.09.2013
comment
Страхотен @ryan-lecompte! Не мислех, че _* може да се използва там. rest изглежда излишно в този случай: m.asScala.collect { case (k, JavaCollection(a, b, _*)) => k -> (a + b) } е достатъчно, но би било полезно за други случаи, когато е необходима опашката. Все още се чудя дали има начин :: да работи върху java колекции и други персонализирани колекции. Например класът/операторът case е дефиниран за List, но все още ще работи на Seq, защо... - person Mortimer; 09.09.2013
comment
Всъщност мисля, че :: работи само за List. Например, това е неуспешно: val (a :: b) = Vector(1,2). Работи за Seq(1,2), защото по подразбиране Scala използва List за внедряването на Seq. :) - person Ryan LeCompte; 09.09.2013
comment
объркващо малко Seq! IMO :: трябва да работи за всичко, подобно на LinearSeq: Линейните последователности се дефинират от гледна точка на три абстрактни метода, за които се предполага, че имат ефективни реализации. Това са: [head, tail, isEmpty] - person Mortimer; 09.09.2013