具有上界的函数中类型推断的奇怪行为

Ser*_*nko 7 scala type-inference lower-bound

在实现中更改上限时进入这种奇怪的行为,但忘记在界面中更改它.我认为最后一个语句不应该编译,但它确实会返回意外的结果.

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 => () }
Run Code Online (Sandbox Code Playgroud)

编辑

正如@ som-snytt提到的-Xprint:typer选项,我们可以看到编译器实际推断出的内容:

s.doWork[Base with SuperBaseImpl]
Run Code Online (Sandbox Code Playgroud)

这就解释了为什么我们得到"接口基础".但我仍然不太明白在这种情况下类型推断是如何以及为什么会起作用的.

bde*_*dew 1

请注意,您的代码所说的是:

方法 ServeImp.doWork 必须接受一个参数,该参数是“必须接受某个类 T 的函数,该类 T 是 Base 和 Superbase 的子类”

SuperBaseImpl 不是 Base 的子类,但这不是错误,因为可能存在一个“用 Base 扩展 SuperBaseImpl”的类 X 来满足该要求。

当类型推断发生时,T 被解析为“foo.Base with foo.SuperBaseImpl”,它满足上述所有要求。runtimeClass 是接口 Base,因为在运行时无法在 JVM 中描述该类型,但如果您执行 manifest.toString - 您将看到正确的类型。

没有真正的方法可以通过您的示例来证明这一点,但请考虑以下事项:

trait SuperBase
trait Base extends SuperBase

class SuperBaseImpl(val a: String) extends SuperBase

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

object ServiceImpl extends Service {
  override def doWork[T <: SuperBase : Manifest](body: T => String): (T) => String =
    x => "Manifest is '%s', body returned '%s'".format(manifest[T].toString(), body(x))
}

val s: Service = ServiceImpl

val f = s.doWork { x: SuperBaseImpl => x.a }
// f: Base with SuperBaseImpl => String = <function1>

f(new SuperBaseImpl("foo") with Base)
// res0: String = Manifest is 'Base with SuperBaseImpl', body returned 'foo'

f(new SuperBaseImpl("foo"))
// compile error 
Run Code Online (Sandbox Code Playgroud)

在这里,我让 doWork 返回另一个接受 T 的函数,您可以看到它解析的内容,并且您实际上可以调用它,并且如果您传递与所有类型的约束匹配的内容,它将正常工作。

添加:

另请注意,您的类层次结构根本没有必要显示该行为,它们可以完全不相关。

trait A
trait B

def m[T <: A : Manifest](body: T => Unit) = manifest[T].toString()

m((x: B) => Unit)
//res0: String = A with B
Run Code Online (Sandbox Code Playgroud)