Richeek
Richeek

Reputation: 2220

Scala method unable to infer return type

I failed to understand why, if I specify return type of my function scala compiler complains. I am pretty sure that it is happening because I am using F-bounded polymorphism. Everything in on scala(2.11) shell. Here is my class definition:

trait Base[T <: Base[T]]

case class Derived(id: Long) extends Base[Derived]

This works (without explicit return type):

def buildThisWorks1[T<:Base[T]]() = Derived(1)

However this does not work:

def buildNotWorking[T<:Base[T]]() : T = Derived(1)
<console>:10: error: type mismatch;
 found   : Derived
 required: T
       def buildNotWorking[T<:Base[T]]() : T = Derived(1)

If I typecast Derived class to be of type T it works, which is confusing me.

def buildThisWorks2[T<:Base[T]]() : T =  Derived(1).asInstanceOf[T]

Note that in second definition I make Derived as instance of T (which according to me is redundant since Derived is already a subtype of Base but it works).

def buildThisWorks2[T<:Base[T]]() : T =  Derived(1).asInstanceOf[T]

Upvotes: 0

Views: 360

Answers (1)

Michael Zajac
Michael Zajac

Reputation: 55569

When you don't specify the return type of this method, the compiler infers it:

scala> def buildThisWorks1[T <: Base[T]]() = Derived(1)
buildThisWorks1: [T <: Base[T]]()Derived

Here, the return type is inferred as Derived, and not T. In fact, the type parameter does absolutely nothing here, because the method will always return the Derived type.


The following cannot work, because we do not know that T = Derived. T can be any type that satisfies the constraint T <: Derived[T]. While it's true that there is only one type here that satisfies the condition, the compiler isn't going to assume that T = Derived. You could easily add class Extra extends Base[Extra], which would suddenly and mysteriously cause the method to break (if it worked in the first place).

def buildNotWorking[T <: Base[T]]() : T = Derived(1)

The type-cast works because you are forcing the type of Derived(1) to be that of T. If T is actually Derived, then everything is okay.

def buildThisWorks2[T <: Base[T]]() : T =  Derived(1).asInstanceOf[T]

But the cast is telling the compiler to ignore any type errors, which will cause problems at runtime. If T is not really Derived, you're going to have a problem.

class Bad extends Base[Bad]

scala> buildThisWorks2[Bad]
java.lang.ClassCastException: Derived cannot be cast to Bad
  ... 33 elided

We get a ClassCastException, because T is actually my Bad type, and Derived cannot be cast to it.


All in all, it's unclear what you want this to do. Typically, type parameters are inferred from the parameters of a method. But here, there are no parameters to this method, so there is nothing to infer the parameter type. The compiler is okay with this, because you can still manually supply the type parameter (as I did in the example that caused the exception). The type parameter T cannot be inferred from the return value of the method, because that generally should depend on the caller via parameters (or be static).

Essentially, the type parameter for these methods don't serve a purpose, and are completely ignored since you're returning the same type Derived every time. Something that would make more sense would accept a Base[T] as a parameter, do something with it, and return it (maybe?).

def doSomething[T <: Base[T]](t: T): T = t // not very creative

Upvotes: 2

Related Questions