6

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] ?

jwinandy
  • 1,739
  • 12
  • 22

3 Answers3

3

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)
Matthew Farwell
  • 60,889
  • 18
  • 128
  • 171
  • Nice catch ! How can I prevent the function from being missused ? (Idealy, it shouldn't compile) – jwinandy Apr 12 '12 at 13:10
  • One quick and easy way would be to add an explicit argument, the class: collectFirstOfT[T](cls: Class[T], la: List[_]), then call like: collectFirstOfT(classOf[B], List(new A, new B)). This would return an Option[B] as expected. – Matthew Farwell Apr 12 '12 at 13:38
3

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.

Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681
0

Because compiler can't infer T from agruments, so you must write it explicitly. In first case collect accepts all list.

Sergey Passichenko
  • 6,920
  • 1
  • 28
  • 29
  • `T` (or `Option[T]`) is only for the return type. `{case x if m.erasure.isAssignableFrom(x.getClass) => x}` is a `PartialFunction[-Any,+Any]`. – jwinandy Apr 12 '12 at 12:33