【问题标题】:Why doesn't Scala fully infer type parameters when type parameters are nested?为什么嵌套类型参数时Scala不完全推断类型参数?
【发布时间】:2025-12-08 08:45:01
【问题描述】:

考虑以下 Scala 代码:

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 )
}

最后一行new Example( new ConcreteB )编译失败,报错如下:

错误:推断的类型参数 [ConcreteB,Nothing] 不符合类示例的类型参数边界 [U <: b a>

ConcreteB 拥有解析 U T 所需的所有数据。我在这里遗漏了什么?

【问题讨论】:

标签: scala


【解决方案1】:

Kipton 接近了他的高级解决方案。不幸的是,他绊倒了似乎是 Scala

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

【讨论】:

  • 很接近,但是 ConcreteB 并不是很具体。无需显式参数化 ConcreteB,这对您有用吗?
  • 如果没有 ConcreteB 的显式参数化,我无法进行推理(但我最初提到的类型成员方法可以正常工作)。
【解决方案2】:

(另请参阅两个相关问题:Scala fails to infer the right type argumentsType infered to Nothing in Scala

这看起来像是 Scala 类型推断的限制,这是故意没有指定的。作为解决方法,您可以通过将 T 设为 B 的类型 member 而不是参数来进行推断,

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 )
}

当使用类型成员时,知道它们可以通过细化作为类型参数出现是很有用的,正如 Miles Sabin 对Why is this cyclic reference with a type projection illegal?的回答中所述:

在 Jean-Philippe Pellet 对a related question 的回答中,类型推断通过使类型参数更高种类来辅助。如果你ConcreteB 中引入一个额外的类型参数,那么类型推断就可以工作了,

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 在下面给出了神秘的错误消息,但 Miles Sabin 指出这是一个错误,将在 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] )
             ^

【讨论】:

  • 在下面继续讨论 Miles 的回答。
  • Miles 的答案现在在上面,因为它已成为公认的答案。
【解决方案3】:

我在 GitHub 上写了a document of type inference workarounds 供我自己学习。

我觉得有用的一些简单规则是:

  • 无法推断类型参数的类型参数: Scala 类型推断只看到 参数列表 中指定的类型(不要与 type 混淆参数列表)。

  • 先前的参数不用于推断未来的参数:类型信息仅在参数列表中流动,而不是参数。 p>


但是,在这个特定的示例中,类型成员是前进的方向(感谢@Kipton Barros!)

【讨论】:

【解决方案4】:

Scala 3 中的 works 改进了推理,因此不再需要上述解决方法。例如,类型参数不必总是出现在要推断的(值)参数列表中,因此我们可以编写

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

而不是

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

例如

➜  ~ 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)

我们看到F 被推断为ListA 被推断为Int

【讨论】: