Fabio Lombardelli
Fabio Lombardelli

Reputation: 23

Type inference with type aliases and multiple parameter list function

Compiling this code

case class MyType()
object TestMe extends App {
  type Fun[T] = T => Int
  def myFun[T](x: T): Int = ???
  def matcher[T](f: Fun[T])(p: T): Int = ???
  var f = myFun[MyType] _
  val p = MyType()
  matcher(f)(p)
}

fails with this error:

Error:(16, 11) type mismatch;
 found   : ... MyType => Int
 required: ... TestMe.Fun[T]
    (which expands to)  T => Int
  matcher(f)(p)

Changing the code as shown below fixes the problem:

case class MyType()
object TestMe extends App {
  type Fun[T] = T => Int
  def myFun[T](x: T): Int = ???
  def matcher[T](f: Fun[T])(p: T): Int = ???
  var f: Fun[MyType] = myFun[MyType] // <-- Explicit type
  val p = MyType()
  matcher(f)(p)
}

Also changing the arguments order fixes the problem:

case class MyType()
object TestMe extends App {
  type Fun[T] = T => Int
  def myFun[T](x: T): Int = ???
  def matcher[T](p: T)(f: Fun[T]): Int = ??? // <-- Flipping the argument, so the first argument have explicitly the parametric type
  var f = myFun[MyType] _
  val p = MyType()
  matcher(p)(f)  // <-- Calls with flipped arguments
}

My understanding (I guess due to my lack of Scala knowledge) is that 'type' is just creating type aliases, but does not look like that. Can someone explain the reason for the compilation failure?

Thanks

Upvotes: 2

Views: 237

Answers (1)

Yuval Itzchakov
Yuval Itzchakov

Reputation: 149538

This is a limitation of type inference and how the typer resolves types at compile time.

In Scala, types can be infered between parameter lists (and not inside them). When you first place p of type T as an argument in the first parameter list, the typer can first bind T to MyType, and then the second parameter list for knows the f is of Fun[MyType] since it can infer T:

|-- matcher(p)(f) : pt=Unit EXPRmode (site: method main in Test)
|    |    |    |    |-- matcher(p) BYVALmode-EXPRmode-FUNmode-POLYmode (silent: method main in Test)
|    |    |    |    |    |-- matcher BYVALmode-EXPRmode-FUNmode-POLYmode (silent: method main in Test)
|    |    |    |    |    |    [adapt] [T](p: T)(f: Fun[T])Int adapted to [T](p: T)(f: Fun[T])Int
|    |    |    |    |    |    \-> (p: T)(f: Fun[T])Int
|    |    |    |    |    |-- p BYVALmode-EXPRmode-POLYmode (silent: method main in Test)
|    |    |    |    |    |    \-> MyType
|    |    |    |    |    solving for (T: ?T)
|    |    |    |    |    \-> (f: Fun[MyType])Int
|    |    |    |    |-- f : pt=Fun[MyType] BYVALmode-EXPRmode (site: method main in Test)
|    |    |    |    |    \-> MyType => Int

The other way around doesn't work, the compiler can't infer T being MyType from a function of type MyType => Int (remember that functions maybe also be contravariant in their argument type):

-- matcher(f)(p) : pt=Unit EXPRmode (site: method main in Test)
|    |    |    |    |-- matcher(f) BYVALmode-EXPRmode-FUNmode-POLYmode (silent: method main in Test)
|    |    |    |    |    |-- matcher BYVALmode-EXPRmode-FUNmode-POLYmode (silent: method main in Test)
|    |    |    |    |    |    [adapt] [T](f: Fun[T])(p: T)Int adapted to [T](f: Fun[T])(p: T)Int
|    |    |    |    |    |    \-> (f: Fun[T])(p: T)Int
|    |    |    |    |    |-- f : pt=Fun[?] BYVALmode-EXPRmode-POLYmode (silent: method main in Test)
|    |    |    |    |    |    \-> MyType => Int
|    |    |    |    |    solving for (T: ?T)
|    |    |    |    |    [search #1] start `MyType => Int`, searching for adaptation to pt=(MyType => Int) => Fun[T] (silent: method main in Test) implicits disabled
|    |    |    |    |    [search #2] start `MyType => Int`, searching for adaptation to pt=(=> MyType => Int) => Fun[T] (silent: method main in Test) implicits disabled

As you've noted, switching the parameter lists works. You can also explicitly help the compiler infer the type by stating it on the matcher method:

matcher[MyType](f)(p)

Upvotes: 3

Related Questions