【问题标题】:Scala: Filtering collection based on type parameterScala:基于类型参数过滤集合
【发布时间】:2012-07-03 23:19:38
【问题描述】:

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

下面是一些不能正常工作的代码:

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 = F)添加到Bar,我可以像case x if (x.memberType &lt;:&lt; ReadableFoo) =&gt; x一样打开?

更新

0__ 很快找到了解决原始问题的好方法。当 case 类字段本身是一个集合时,会稍作修改:

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 可能为空,因此没有要测试的元素。

【问题讨论】:

  • This 可能会有所帮助。
  • @missingfaktor 是的,我想将所有类型参数打印到字符串,然后使用字符串树进行我自己的临时继承测试是一种方法。

标签: scala collections type-erasure


【解决方案1】:

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

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) =&gt; Bar(f))。


使用异类Seq 我猜你是在Seq 本身上寻找collect

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

【讨论】:

  • 这可以链接起来吗,就像Bar 有一个foo: Seq[F] 而不仅仅是foo: F?如果这样可以提高清晰度,我可以编辑问题。
  • 只要你有提取器,就可以。它将是 case Bar(Seq(f: Foo2) =&gt; Bar(Seq(f)) - 如果您需要返回类型为 Bar[Seq[Foo2]],您需要强制转换或重建对象
  • Seq(f: Foo2) 不是只匹配一个元素的序列吗?
  • 您是否计划在 Seq 中拥有不同的 Foo 子类型,或者都相同,例如Seq[Foo2]?
  • 它们将是不同的子类型。它可以是Foo2Foo3 中的Seq,这样BarSeq 将被参数化为ReadableFoo
【解决方案2】:

我不知道提取器技巧中的类型检查,所以我对您的第一个问题的最初解决方案会有点不同。我会为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}

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

【讨论】:

  • 这似乎工作得很好(根据 Sciss 的建议,我在 x 上删除了 asInstanceOf 之后)。有什么问题吗,因为如果就这么简单,Scala 编译器似乎会自动注入那些 def manifestif 语句?
  • 好吧,我想它会增加一些开销。编译器仅按需注入清单本身,但始终注入数组,因为它们实际上需要映射到 Java 数组。对于其他所有事情,它们确实不是那么频繁地需要并且不太频繁地需要。大多数情况下,简单地使用案例类/提取器并匹配值就足够了。
猜你喜欢
  • 2011-04-04
  • 2011-01-14
  • 2011-04-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-06
  • 2011-11-18
  • 1970-01-01
相关资源
最近更新 更多