4

假设我可以控制这两个类并且我需要协变过滤,那么根据这些对象的类型参数过滤对象集合的最佳方法是什么?

这是一些无法正常工作的代码:

trait Foo
case class Foo1() extends Foo
trait ReadableFoo extends Foo {def field: Int}
case class Foo2(field: Int, flag: Boolean) extends ReadableFoo
case class Foo3(field: Int, name: String) extends ReadableFoo

case class Bar[+F <: Foo](foo: F)

val seq = Seq(
  Bar[Foo1](Foo1()),
  Bar[Foo2](Foo2(1,true)), 
  Bar[Foo3](Foo3(1,"Fooz"))
)

// Should keep one
val first = seq collect {case x: Bar[Foo2] => x}

// Should keep two
val both = seq collect {case x: Bar[ReadableFoo] => x}

现在,我知道这是因为case x: Bar[Foo1]通过类型擦除将其转换为case x: Bar[_]编译后。我一直无法使用清单来解决这个问题。有没有办法添加一个成员类型(即memberType = FBar,我可以像这样打开case x if (x.memberType <:< ReadableFoo) => x

更新

0__ 很快就找到了解决原问题的好办法。一个轻微的修改是当案例类字段本身是一个集合时:

case class Bar[+F <: Foo](foo: Seq[F])

val seq = Seq(
  Bar[Foo1](Seq(Foo1())),
  Bar[Foo2](Seq(Foo2(1,true))),
  Bar[ReadableFoo](Seq(Foo2(1,true), Foo3(1,"Fooz")))
)

// Should keep one
val first = seq collect {case x: Bar[Foo2] => x}

// Should keep two
val both = seq collect {case x: Bar[ReadableFoo] => x}

我不确定这是否可行,因为Seq可能是空的,因此没有要测试的元素。

4

2 回答 2

8

您可以将提取器与类型检查结合使用:

val first = seq collect { case x @ Bar(_: Foo2)        => x }
val both  = seq collect { case x @ Bar(_: ReadableFoo) => x }

但是返回类型仍然是List[Bar[Foo]]......所以如果你需要它,使用这种方法你需要强制转换或重新构造Bar对象(case Bar(f: Foo2) => Bar(f))。


我猜你正在Seq寻找一个异类?collectSeq

case class Bar(seq: Seq[Foo])

def onlyFoo2(b: Bar) = Bar(b.seq.collect { case f: Foo2 => f })

onlyFoo2(Bar(Seq(Foo1(), Foo2(1, true))))
于 2012-07-03T23:36:33.450 回答
1

我不知道提取器技巧中的类型检查,因此我对您的第一个问题的最初解决方案会有所不同。我会提供一个提取器ReadableFoo

object ReadableFoo { def unapply(x: ReadableFoo) = Some(x.field) }

然后你可以做

val first = seq collect { case x @ Bar(Foo2(_,_)) => x }
val both  = seq collect { case x @ Bar(ReadableFoo(_)) => x }

但是对于您更新的代码,我认为您需要拖动清单。

case class Bar[+F <: Foo : Manifest](foo: Seq[F]) { 
    def manifest = implicitly[Manifest[_ <: F]] 
}

由于 Bar 是协变的并且 Manifest 是不变的,我们不能简单地承诺返回 Manifest[F] 而是返回 F 的某个子类型的 Manifest。(我想这是您尝试使用清单时的问题?)
之后你可以做

val first = seq collect {case x if x.manifest <:< manifest[Foo2] => x}
val both = seq collect {case x if x.manifest <:< manifest[ReadableFoo] => x}

尽管如此,使用清单总是让人觉得有点 hacky。我会看看我是否可以使用不同的方法并尽可能少地依赖类型匹配和具体化。

于 2012-07-04T03:04:16.493 回答