【问题标题】:Scala: Selecting a subset of types from a generic collection without std collections. (collect)Scala:从没有标准集合的泛型集合中选择类型的子集。 (搜集)
【发布时间】:2011-08-16 08:22:42
【问题描述】:

在我的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:

事实证明,我设法通过使用 PartialFunction 像在 std lib 中一样生成了一些东西。

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 }

不过,任何关于清理此问题的替代方案或提示仍然很棒!

【问题讨论】:

    标签: generics scala collections type-erasure


    【解决方案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))        
    ...
    

    【讨论】:

    • 这正是我所了解的,谢谢!围绕那些 ClassManifests 摆动有点笨拙(不是你的代码,而是一般来说),但我认为它毕竟是一个非常高级的功能。
    【解决方案2】:

    清单可用于解决擦除问题。由于我们只对清单的擦除感兴趣,因此我们在这里使用 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)) 
      }
    

    以下提供了 Array[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
          }
      }
    

    编辑:我只是注意到我没有回答操作员的要求。无论如何我都会留下我的答案,也许它对某人有用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-08-14
      • 2021-09-29
      • 2013-11-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多