scala / bug

Scala 2 bug reports only. Please, no questions — proper bug reports only.
https://scala-lang.org
232 stars 21 forks source link

short syntax to filter a collection by a single type #4198

Open scabug opened 13 years ago

scabug commented 13 years ago

Using the collection API I discovered one way to filter out a subtype of a collection (I'll use List in my example):

scala> class Foo
defined class Foo

scala> class Bar extends Foo
defined class Bar

scala> val list = List(new Foo, new Bar)
list: List[Foo] = List(Foo@71f84d01, Bar@6487b71b)

scala> list collect { case x: Bar => x }
res0: List[Bar] = List(Bar@6487b71b)

... so I have extracted all the Bars from a List[Foo] and now have a List[Bar].

It would be even nicer and shorter to write something like this:

scala> list narrow Bar
res0: List[Bar] = List(Bar@6487b71b)

For this to work, one would need a method signature like in the following example:

scala> class RichList[A](l: List[A]) {
     |   def narrow [B] (c: Class[B]): List[B] =
     |     l collect { case x: B => x }
     | }
<console>:10: warning: abstract type B in type pattern B is unchecked since it is eliminated by erasure
           l collect { case x: B => x }
                               ^
defined class RichList

... but, as you can see, this won't work due to type erasure. I'm also not sure whether to use 'c: Class[B]' for the parameter is the right choice.

The question remains, whether there is a type-safe way to implement this filter method to have the advantage of using the short syntax I presented above to filter a List by a single type.

Best regards Christian Krause aka wookietreiber

scabug commented 13 years ago

Imported From: https://issues.scala-lang.org/browse/SI-4198?orig=1 Reporter: Christian Krause (wookietreiber)

scabug commented 13 years ago

Christian Krause (wookietreiber) said: sry almost forgot:

Scala code runner version 2.8.1.final -- Copyright 2002-2010, LAMP/EPFL

java version "1.6.0_23" Java(TM) SE Runtime Environment (build 1.6.0_23-b05) Java HotSpot(TM) 64-Bit Server VM (build 19.0-b09, mixed mode)

scabug commented 13 years ago

@paulp said: This sort of thing is dangerous, but you can do something like this. (I can't settle for not returning the same collection type.)

import scala.collection.{ mutable, immutable, generic }
import generic._

class RichTrav[T, CC[X] <: Traversable[X]](coll: CC[T]) {
  def narrow[U <: AnyRef](implicit cm: ClassManifest[U], bf: CanBuildFrom[CC[T], U, CC[U]]): CC[U] = {
    val clazz = cm.erasure
    val buf = bf(coll)
    coll foreach { x =>
      if (clazz.isAssignableFrom(x.asInstanceOf[AnyRef].getClass))
        buf += x.asInstanceOf[U]
    }
    buf.result
  }
}

class A
class B extends A
class C extends B

object Test {
  implicit def mkRich[T, CC[X] <: Traversable[X]](coll: CC[T]) = new RichTrav[T, CC](coll)

  def main(args: Array[String]): Unit = {
    val xs: List[Any] = List(new A, new B, new C, List(1), 5, List(new A))
    println(xs.narrow[B])
    println(xs.toIndexedSeq.narrow[B])
    println(xs.toBuffer.narrow[B])
  }
}

But now you don't get unchecked warnings, not that people seem to pay much attention to them.

scala> xs.narrow[List[Int]].flatten.sum
java.lang.ClassCastException: A cannot be cast to java.lang.Integer
    at scala.runtime.BoxesRunTime.unboxToInt(Unknown Source)
    at scala.math.Numeric$$IntIsIntegral$$.plus(Numeric.scala:45)
    at scala.collection.TraversableOnce$$$$anonfun$$sum$$1.apply(TraversableOnce.scala:322)
    at scala.collection.LinearSeqOptimized$$class.foldLeft(LinearSeqOptimized.scala:113)
    at scala.collection.immutable.List.foldLeft(List.scala:45)
    at scala.collection.TraversableOnce$$class.sum(TraversableOnce.scala:322)
scabug commented 13 years ago

@lindydonna said: Given that there is a reasonable workaround, I am reassigning to "future release suggestions." Though I agree that this would be a nice enhancement, implementing it is not trivial.

scabug commented 11 years ago

Christian Krause (wookietreiber) said (edited on Dec 10, 2012 10:00:35 PM UTC): I figure this might work:

$ scala
Welcome to Scala version 2.9.2 (OpenJDK 64-Bit Server VM, Java 1.7.0_09).
Type in expressions to have them evaluated.
Type :help for more information.

scala> class Foo
defined class Foo

scala> class Bar extends Foo
defined class Bar

scala> class Baz extends Bar
defined class Baz

scala> val l = List(new Foo, new Bar, new Baz)
l: List[Foo] = List(Foo@10a44013, Bar@f6aa7ee, Baz@2337022a)

scala> implicit def RichList[A](l: List[A]) = new {
     |   def narrow[X <: A](implicit m: ClassManifest[X]) = l filter m.erasure.isInstance
     | }
RichList: [A](l: List[A])java.lang.Object{def narrow[X <: A](implicit m: ClassManifest[X]): List[A]}

scala> l.narrow[Foo]
res0: List[Foo] = List(Foo@10a44013, Bar@f6aa7ee, Baz@2337022a)

scala> l.narrow[Bar]
res1: List[Foo] = List(Bar@f6aa7ee, Baz@2337022a)

scala> l.narrow[Baz]
res2: List[Foo] = List(Baz@2337022a)

... a little more concise than Pauls example.