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

Я пытаюсь написать «деконструктор» для java-коллекций в scala.

С большинством коллекций scala я могу использовать класс case :: для деконструкции коллекции в начале/конце:

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! ИМО :: должен работать для всего, что похоже на LinearSeq: линейные последовательности определяются с точки зрения трех абстрактных методов, которые, как предполагается, имеют эффективные реализации. Это: [head, tail, isEmpty] - person Mortimer; 09.09.2013