开发者

Scala: Selecting a subset of types from a generic collection without std collections. (collect)

Following my previous question came a quick and great answer, however it turned out my example didn't match my actual production code well enough. In summary I'm in need of a new implementation of the collect method.

The second fruit world (with some pretty funky fruit trees):

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

Does not work because of type erasure:

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: Arr开发者_JS百科ay[Fruit] = Array($anon$2@5a4b99fa)

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

EDIT, SOLUTION1:

Turns out i managed to produce something which works by using the PartialFunction as in the 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))
}

Use case:

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

Any alternatives or tips on cleaning this up would still be great though!


I think what you need is 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])
}

With every call of select an implicit parameter containing actual class information will be passed. You can now make class checks at runtime, and also you can return Array of more specific type.

This produces desired result:

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

Alternatively, you can use context bound syntax:

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


Manifests can be used to work around erasure. Since we're only interested in the manifest's erasure, we use a ClassManifest here because it's more lightweight.

The following is simple and works but the return type of select is Array[Fruit], not 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)) 
  }

The following provides a return type of Array[F] but is a little more involved.

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

Edit: I just noticed that I didn't answer what the op asked. I will leave my answer anyway, maybe it's useful to someone.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜