Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to read the class of a Scala object extending Any but not AnyRef?

I have an heterogeneous List like the following one:

val l = List(1, "One", true)

and I need to filter its objects by extracting only the ones belonging to a given Class. For this purpose I wrote a very simple method like this:

def filterByClass[A](l: List[_], c: Class[A]) =
  l filter (_.asInstanceOf[AnyRef].getClass() == c)

Note that I am obliged to add the explicit conversion to AnyRef in order to avoid this compilation problem:

error: type mismatch;
found   : _$1 where type _$1
required: ?{val getClass(): ?}
Note that implicit conversions are not applicable because they are ambiguous:
both method any2stringadd in object Predef of type (x: Any)scala.runtime.StringAdd
and method any2ArrowAssoc in object Predef of type [A](x: A)ArrowAssoc[A]
are possible conversion functions from _$1 to ?{val getClass(): ?}
l filter (_.getClass() == c)

However in this way the invocation of:

filterByClass(l, classOf[String])

returns as expected:

List(One)

but of course the same doesn't work, for example, with Int since they extends Any but not AnyRef, so by invoking:

filterByClass(l, classOf[Int])

the result is just the empty List.

Is there a way to make my filterByClass method working even with Int, Boolean and all the other classes extending Any?

like image 678
Mario Fusco Avatar asked Apr 10 '11 12:04

Mario Fusco


2 Answers

The collect method already does what you want. For example to collect all Ints in a collection you could write

xs collect { case x: Int => x }

This of course only works when you hardcode the type but as primitives are handled differently from reference types it is actually better to do so. You can make your life easier with some type classes:

case class Collect[A](collect: PartialFunction[Any,A])

object Collect {

  implicit val collectInt: Collect[Int] = Collect[Int]({case x: Int => x})

  // repeat for other primitives

  // for types that extend AnyRef
  implicit def collectAnyRef[A <: AnyRef](implicit mf: ClassManifest[A]) =
    Collect[A]({ case x if mf.erasure.isInstance(x) => x.asInstanceOf[A] })
}

def collectInstance[A : Collect](xs: List[_ >: A]) =
  xs.collect(implicitly[Collect[A]].collect)

Then you can use it without even passing a Class[A] instance:

scala> collectInstance[Int](l)
res5: List[Int] = List(1)

scala> collectInstance[String](l)
res6: List[String] = List(One)
like image 188
Moritz Avatar answered Nov 15 '22 08:11

Moritz


Using isInstanceOf:

scala> val l = List(1, "One", 2)
l: List[Any] = List(1, One, 2)

scala> l . filter(_.isInstanceOf[String])
res1: List[Any] = List(One)

scala> l . filter(_.isInstanceOf[Int])
res2: List[Any] = List(1, 2)

edit: As the OP requested, here's another version that moves the check in a method. I Couldn't find a way to use isInstanceOf and so I changed the implementation to use a ClassManifest:

def filterByClass[A](l: List[_])(implicit mf: ClassManifest[A]) =
  l.filter(mf.erasure.isInstance(_))

Some usage scenarios:

scala> filterByClass[String](l)
res5: List[Any] = List(One)

scala> filterByClass[java.lang.Integer](l)
res6: List[Any] = List(1, 2)

scala> filterByClass[Int](l)
res7: List[Any] = List()

As can be seen above, this solution doesn't work with Scala's Int type.

like image 35
Jawher Avatar answered Nov 15 '22 07:11

Jawher