Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange behavior of type inference in function with upper bound

Ran into this strange behavior when changed upper bound in the implementation, but forgot to change it in the interface. I think last statement should not compile, but it does and returns unexpected result.

trait SuperBase
trait Base extends SuperBase

class SuperBaseImpl extends SuperBase

trait Service {
  def doWork[T <: Base : Manifest](body: T => Unit): String
  def print[T <: Base : Manifest]: String
}

object ServiceImpl extends Service {
  override def doWork[T <: SuperBase : Manifest](body: T => Unit): String =
    print[T]
  def print[T <: SuperBase : Manifest]: String =
    manifest[T].runtimeClass.toString
}

val s: Service = ServiceImpl

// does not compile as expected
// s.print[SuperBaseImpl]

// returns "interface Base"
s.doWork { x: SuperBaseImpl => () }

Edit

As @som-snytt mentioned with -Xprint:typer option we can see what compiler actually infers:

s.doWork[Base with SuperBaseImpl]

This explains why we are getting "interface Base". But I still not quite understand how and why type inference work in this case.

like image 213
Sergey Passichenko Avatar asked Nov 12 '15 17:11

Sergey Passichenko


1 Answers

With -Xprint:typer, you'll see what the compiler infers for T:

s.doWork[Base with SuperBaseImpl]

What is the bound trying to express? Functions are co-variant in the parameter, so you are expressing that body must accept a certain arg of a sufficiently narrow type. Normally, you require that a function must deal with a wide type.

Maybe you intended a lower bound.

scala> trait SuperBase
defined trait SuperBase

scala> trait Base extends SuperBase
defined trait Base

scala> class SuperBaseImpl extends SuperBase
defined class SuperBaseImpl

scala> trait Service { def f[A >: Base : Manifest](g: A => Unit): String }
defined trait Service

scala> object Impl extends Service { def f[A >: Base : Manifest](g: A => Unit) = manifest[A].runtimeClass.toString }
defined object Impl

scala> (Impl: Service).f { x: Base => () }
res0: String = interface Base

scala> (Impl: Service).f { x: SuperBase => () }
res1: String = interface SuperBase

scala> (Impl: Service).f { x: SuperBaseImpl => () }
<console>:17: error: inferred type arguments [SuperBaseImpl] do not conform to method f's type parameter bounds [A >: Base]
       (Impl: Service).f { x: SuperBaseImpl => () }
                       ^
<console>:17: error: type mismatch;
 found   : SuperBaseImpl => Unit
 required: A => Unit
       (Impl: Service).f { x: SuperBaseImpl => () }
                                            ^
<console>:17: error: No Manifest available for A.
       (Impl: Service).f { x: SuperBaseImpl => () }
                         ^

scala> object Impl extends Service { def f[A >: SuperBase : Manifest](g: A => Unit) = manifest[A].runtimeClass.toString }
<console>:14: error: overriding method f in trait Service of type [A >: Base](g: A => Unit)(implicit evidence$1: Manifest[A])String;
 method f has incompatible type
       object Impl extends Service { def f[A >: SuperBase : Manifest](g: A => Unit) = manifest[A].runtimeClass.toString }
                                         ^
like image 192
som-snytt Avatar answered Sep 23 '22 15:09

som-snytt