Scala:从没有std集合的泛型集合中选择类型的子集。 (搜集)

时间:2011-08-16 08:22:42

标签: generics scala collections type-erasure

在我的previous question之后得到了一个快速而且很好的答案,然而事实证明我的例子与我的实际生产代码不匹配。总之,我需要一个新的collect方法实现。

第二个水果世界(有一些非常时髦的果树):

class Fruit {
    var seeds:Array[Fruit] = Array()
    def naiveCollect[F <: Fruit]:Array[Fruit] = this match {
        case f:F => Array(this)
        case _ => seeds.map(_.select[F]).flatten.toArray
    }
}

class Apple extends Fruit
class Pear extends Fruit
class GrannySmith extends Apple

由于类型擦除而无效:

var tree = new Fruit { seeds = Array(
                new Apple,
                new Pear,
                new GrannySmith,
                new Pear { seeds = Array(
                    new Apple,
                    new Pear)},
                new Apple)}

scala> tree.naiveCollect[Apple]
res1: Array[Fruit] = Array($anon$2@5a4b99fa)

// wanted output: Apple, GrannySmith, Apple, Apple

编辑,解决方案1:

事实证明我设法通过在std lib中使用PartialFunction来生成一些可行的东西。

class Fruit {
    ...
    def clumsyCollect[F](pf:PartialFunction[Fruit, F]):Seq[F] = 
        if (pf.isDefinedAt(this))
            List(pf(this))
        else
            seeds.flatMap(_.selectPartial[F](pf))
}

用例:

tree.clumsyCollect { case a:Apple => a }

有关清理此事的任何替代方法或提示仍然会很棒!

2 个答案:

答案 0 :(得分:1)

我认为您需要的是scala.reflect.ClassManifest

class Fruit {
    var seeds:Array[Fruit] = Array()
    def select[F <: Fruit](implicit cm: ClassManifest[F]): Array[F] = 
      if (cm.erasure.isInstance(this))        
        Array(this.asInstanceOf[F])
      else 
        seeds.flatMap(_.select[F])
}

每次调用select时,都会传递包含实际类信息的隐式参数。您现在可以在运行时进行类检查,也可以返回更具体类型的Array

这会产生所需的结果:

scala> tree.select[Apple]
res12: Array[Apple] = Array(Apple@10fa4d, GrannySmith@a10ca8, Apple@14611ec, Apple@142b533)

或者,您可以使用上下文绑定语法:

def select[F <: Fruit : ClassManifest]: Array[F] = 
  if (classManifest[F].erasure.isInstance(this))        
...

答案 1 :(得分:1)

清单可用于解决擦除问题。由于我们只对清单的擦除感兴趣,我们在这里使用ClassManifest因为它更轻量级。

以下内容很简单,但是select的返回类型是Array [Fruit],而不是Array [F]。

  class Fruit {
      var seeds: Array[Fruit] = Array()
      def select[F <: Fruit](implicit m: ClassManifest[F]): Array[Fruit] =
        seeds.filter(s => m.erasure.isInstance(s)) 
  }

以下提供了一个返回类型的数组[F],但更多一些。

  class Fruit {
      var seeds: Array[Fruit] = Array()
      def select[F <: Fruit](implicit m: ClassManifest[F]): Array[F] = {
        seeds.foldLeft(Array.newBuilder[F]) { (b, s) =>
          if(m.erasure.isInstance(s)) b += s.asInstanceOf[F] else b
        }.result
      }
  }

编辑:我刚注意到我没有回答操作的问题。无论如何,我会留下我的答案,也许这对某人有用。