Фильтр коллекции Scala по типу - PullRequest
4 голосов
/ 07 сентября 2010

Я новичок в Scala и столкнулся со следующей проблемой:

Я хочу получить вложенную коллекцию существующей коллекции, которая содержит только элементы определенного типа. Следующие работы:

class C(val name : String)
class D(name : String) extends C(name) { }

val collection = Set[C](new C("C1"),new D("D1"),new C("C2"),new D("D2"))
collection.collect{case d : D => d}.size must be === 2 // works

Но когда я пытаюсь расширить классы коллекции с помощью метода "onlyInstancesOf [Type]", это не работает. Первая моя реализация:

object Collection {
    implicit def extendScalaCollection[E](coll : Traversable[E]) = new CollectionExtension[E](coll)
}

class CollectionExtension[E](coll : Traversable[E]) {

    def onlyInstancesOf[SpecialE <: E] : Traversable[SpecialE] = {
        coll.collect({case special : SpecialE => special}).asInstanceOf[Traversable[SpecialE]]
    }
}

Поэтому, когда я использую это расширение и выполняю:

collection.onlyInstancesOf[D].size must be === 2

Я получаю ошибку, что .size вернул 4, а не 2. Кроме того, я проверил, что результат на самом деле содержит C1 и C2, хотя не должен.

Когда я делаю:

collection.onlyInstancesOf[D].foreach(e => println(e.name))

Я получаю исключение:

java.lang.ClassCastException: CollectionsSpec$$anonfun$1$C$1 cannot be cast to CollectionsSpec$$anonfun$1$D$1

Таким образом, очевидно, что результирующий набор все еще содержит элементы, которые должны были быть отфильтрованы.

Я не понимаю, почему это происходит, кто-нибудь может объяснить это?

Edit: Scala: бегун с кодом Scala версии 2.8.0.final

Ответы [ 4 ]

10 голосов
/ 07 сентября 2010

Обратите внимание на предупреждения компилятора и добавьте -unchecked параметры командной строки scala.

M:\>scala -unchecked
Welcome to Scala version 2.8.0.final (Java HotSpot(TM) Client VM, Java 1.6.0_21)
.
Type in expressions to have them evaluated.
Type :help for more information.

scala> class CollectionExtension[E](coll : Traversable[E]) {
     |
     |     def onlyInstancesOf[SpecialE <: E] : Traversable[SpecialE] = {
     |         coll.collect({case special : SpecialE => special}).asInstanceOf[Traversable[SpecialE]]
     |     }
     | }
<console>:8: warning: abstract type SpecialE in type pattern SpecialE is unchecked since it is eliminated by erasure
               coll.collect({case special : SpecialE => special}).asInstanceOf[Traversable[SpecialE]]
                                            ^
defined class CollectionExtension

Предупреждение означает, что лучшее, что может сделать компилятор, эквивалентно:

coll.collect({case special : AnyRef => special}).asInstanceOf[Traversable[_]]

Более подробное объяснение удаления типа и способов его обхода с помощью манифестов см. В

https://stackoverflow.com/questions/tagged/type-erasure+scala

6 голосов
/ 07 сентября 2010

Как уже отмечали другие, манифесты могут спасти вас.Вот пример того, как ограничить себя не примитивами и предположить, что мы не хотим хранить манифесты в наших коллекциях, а вместо этого используем отражение на месте, чтобы разобраться:

class CollectionExtension[E <: AnyRef](coll : Traversable[E]) {
  def onlyInstancesOf[SpecialE <: E](implicit mf : Manifest[SpecialE]) : Traversable[SpecialE] = {
    coll.collect({
      case special if mf.erasure.isAssignableFrom(special.getClass) => special
    }).asInstanceOf[Traversable[SpecialE]]
  }
}

и вот онов действии:

scala> val ce = new CollectionExtension(List(Some(1),Some(5),"This","Fox")) 
ce: CollectionExtension[java.lang.Object] = CollectionExtension@1b3d4787

scala> val opts = ce.onlyInstancesOf[Some[_]]
opts: Traversable[Some[_]] = List(Some(1), Some(5))

scala> val strings = ce.onlyInstancesOf[String] 
strings: Traversable[String] = List(This, Fox)
4 голосов
/ 07 сентября 2010

Scala запускается на JVM, которая, к сожалению, стирает параметры типа во время выполнения: http://en.wikipedia.org/wiki/Generics_in_Java#Type_erasure. В первом примере вы задаете тип в не стертой позиции, и поэтому код выполнения может выполнять сравнение. Во втором примере тип SpecialE стирается, и, следовательно, код будет возвращать все.

Вы можете использовать манифесты scala для восстановления части информации, потерянной при стирании типа:

import scala.reflect.ClassManifest
class CollectionsExtension[E <: AnyRef](coll : Traversable[E]) {
  def onlyInstancesOf[SpecialE <: E](implicit m : Manifest[SpecialE]) : Traversable[SpecialE] = {
    coll.collect({case e if (ClassManifest.singleType(e) <:< m) => e}).asInstanceOf[Traversable[SpecialE]]
  }
}
3 голосов
/ 07 сентября 2010

Как говорится в предупреждении:

<console>:14: warning: abstract type SpecialE in type pattern SpecialE is unchecked since it is eliminated by erasure
               coll.collect({case special : SpecialE => special}).asInstanceOf[Traversable[SpecialE]]

Давайте посмотрим реализацию collect:

def collect[B, That](pf: PartialFunction[A, B])(implicit bf: CanBuildFrom[Repr, B, That]): That = {
  val b = bf(repr)
  for (x <- this) if (pf.isDefinedAt(x)) b += pf(x)
  b.result
}

Обратите внимание, что здесь нет сопоставления с образцом.Это принципиальное отличие - когда вы пишете «collection.collect{case d : D => d}», компилятор точно знает, о каком типе вы говорите: D.

С другой стороны, когда вы пишете coll.collect({case special : SpecialE => special}), компиляторне знает, какой тип SpecialE, потому что SpecialE это просто параметр типа.Поэтому он не может генерировать код, который знает, что такое SpecialE, и во время выполнения больше нет SpecialE - байт-код просто использует java.lang.Object.

...