Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic collect by type in scala

In Scala 2.9.1

With

def collectFirstOfT[T](la: List[_])(implicit m:Manifest[T]) : Option[T] = {
  la.collect{case x if m.erasure.isAssignableFrom(x.getClass) => x}.
    headOption.asInstanceOf[Option[T]]}

class A
class B

why this expression :

val oB:Option[B] = collectFirstOf(List(new A,new B)) 

compiles but collects Some(A),but

val oB =collectFirstOf[B](List(new A,new B))

works fine.

How to infer T from Option[T] ?

like image 435
jwinandy Avatar asked Apr 12 '12 09:04

jwinandy


2 Answers

You have to look at the following line as two separate parts, the left hand side of the = and the right:

val oB: Option[B] = collectFirstOf(List(new A,new B))

What you're expecting here is that the type of the collectFirstOf expression (the rvalue) should be inferred from the type of the value oB. The compiler can't do this. You have to say specifically what type you're expecting. Take the following example:

val v: Long = 1 + 4

The type of the expression 1 + 4 is an Int. This int is then converted into a Long. The compiler doesn't, and can't infer that you want the 1 or the 4 to be Long:

So, to fix your problem, you need to tell the compiler what type you're expecting, otherwise it assumes java.lang.Object:

val oB = collectFirstOf[B](List(new A,new B))

So the manifest gets correctly assigned, and all is well with the world. So why does the following even compile:

val oB:Option[B] = collectFirstOfT(List(new A,new B))
oB: Option[B] = Some(A@10f3a9c)

at first sight, it doesn't seem like this should work, but it does. This is because the collectFirstOfT actually returns an Option[Nothing], which can be safely converted to a Option[B]:

scala> val f = collectFirstOfT(List(new A,new B))
f: Option[Nothing] = Some(A@baecb8)

scala> f.asInstanceOf[Option[B]]
res4: Option[B] = Some(A@baecb8)
like image 155
Matthew Farwell Avatar answered Nov 02 '22 16:11

Matthew Farwell


This:

val oB:Option[B] = collectFirstOfT(List(new A,new B)) 

Is equivalent to this:

val oB:Option[B] = collectFirstOfT[Nothing](List(new A,new B))

Since Nothing is a subclass of everything, then it is assignable from A. Alas, it is also assignable from B, which means you can assign an Option[Nothing] to an Option[B].

Fun fact: that is true because Option is co-variant. If it wasn't, then T would have to be inferred as B, which would make it work.

Fun fact 2: this code does not compile on yesterday's trunk.

like image 20
Daniel C. Sobral Avatar answered Nov 02 '22 17:11

Daniel C. Sobral