开发者

Why doesn't Scala fully infer type parameters when type parameters are nested?

Consider the following Scala code:

abstract class A
abstract class B[T <: A]
class ConcreteA extends A
class ConcreteB extends B[ConcreteA]

class Example[U <: B[T], T <: A]( resolver: U )
object Test {
    new Example( new ConcreteB )
}

The last line new Example( new ConcreteB ) fails to compile with the following error:

error: inferred type arguments [ConcreteB,Nothing] do not conform to class Example's type parameter bounds [U <: B[T],T <: A]

But Concret开发者_Go百科eB has all the necessary data to resolve both U and T. What am I missing here?


Kipton got close with his higher-kinded solution. Unfortunately he tripped over what appears to be a bug in Scala < 2.9.1.RC1. The following works as expected with 2.9.1.RC1 and trunk,

Welcome to Scala version 2.9.1.RC1 (Java HotSpot(TM) Server VM, Java 1.7.0).
Type in expressions to have them evaluated.
Type :help for more information.

scala> abstract class A
defined class A

scala> abstract class B[T <: A]
defined class B

scala> class ConcreteA extends A
defined class ConcreteA

scala> class ConcreteB[T <: A] extends B[T]
defined class ConcreteB

scala> class Example[T <: A, U[X <: A] <: B[X]](resolver: U[T])
defined class Example

scala> new Example(new ConcreteB[ConcreteA])
res0: Example[ConcreteA,ConcreteB] = Example@ec48e7


(See also two related questions: Scala fails to infer the right type arguments and Type infered to Nothing in Scala)

It looks like a limitation of Scala's type inference, which is intentionally not spec'ed. As work-around, you can get inference by making T a type member of B rather than parameter,

abstract class A
abstract class B { type T <: A }
class ConcreteA extends A
class ConcreteB extends B { type T = ConcreteA }
class Example[U <: B]( resolver: U )
object Test {
    new Example( new ConcreteB )
}

When using type members, it's useful to know that they can be surfaced as type parameters using refinement, as in Miles Sabin's answer to: Why is this cyclic reference with a type projection illegal?

In Jean-Philippe Pellet's answer to a related question, type inference was aided by making the type parameter higher kinded. If you introduce an extra type parameter in ConcreteB, then type inference can work,

abstract class A
abstract class B[T <: A]
class ConcreteA extends A
class ConcreteB[T <: A] extends B[T]
class Example[T <: A, U[T0 <: A] <: B[T0]]( resolver: U[T] )
object Test {
  new Example( new ConcreteB[ConcreteA] )
}

Scala 2.9 gives the mysterious error message below, but Miles Sabin points out it is a bug that will be fixed for 2.9.1

<console>:15: error: kinds of the type arguments (ConcreteA,ConcreteB[T0]) do not conform to the expected kinds of the type parameters (type T,type U) in class Example.
ConcreteB[T0]'s type parameters do not match type U's expected parameters: class ConcreteB has one type parameter, but type U has one
         new Example( new ConcreteB[ConcreteA] )
             ^


I have composed a document of type inference workarounds on GitHub for my own learning.

A few simple rules that I find useful are:

  • Type parameters of type parameters cannot be inferred: Scala type inference only sees types specified in the parameter list (not to be confused with type parameter list).

  • Previous parameters are not used to infer future parameters: Type information only flows across parameter lists, not parameters.


However, in this particular example type members are the way forward (thanks @Kipton Barros!)


It works in Scala 3 which has improved inference so the workarounds above are no longer necessary. For example type parameters do not have to always be surfaced in the (value) parameter list to be inferred so we can write

def f[F <: List[A], A](as: F)

instead of

def f[F <: List[A], A](as: F[A])

for example

➜  ~ scala3-repl -version
Scala code runner version 3.0.0-RC2 -- Copyright 2002-2021, LAMP/EPFL
➜  ~ scala3-repl         
scala> def f[F <: List[A], A](as: F) = as                                                                                                                
def f[F <: List[A], A](as: F): F

scala> f(List(42))                                                                                                                                       
val res0: List[Int] = List(42)

where we see F was inferred as List and A was inferred as Int.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜