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

Strange behavior of type inference in function with upper bound

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

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

编辑

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

s.doWork[Base with SuperBaseImpl]

这解释了为什么我们得到 "interface Base"。但我仍然不太明白类型推断在这种情况下如何以及为什么起作用。

它看起来很奇怪,但感觉很好。注意你也可以调用

s.doWork { x: Any => () }

我只是认为类型参数 T 不知何故 "uninhabited"。该方法除了其上限 Base 外无法知道任何关于 T 的信息,因此您获得了 Base 的清单。但是同样你不能做太多,因为它不能构造一个 T 类型的值……所以一切都保持正常。

尝试将签名更改为

def doWork[T <: Base : Manifest](x: T)(body: T => Unit): String

那你就不能这样用了:

s.doWork(123: Int) { x: Any => () }  // no
s.doWork(123: Any) { x: Any => () }  // no

使用 -Xprint:typer,您将看到编译器为 T 推断的内容:

s.doWork[Base with SuperBaseImpl]

绑定试图表达什么?函数在参数中是协变的,因此您表示 body 必须接受某个类型足够窄的参数。通常,您要求函数必须处理宽类型。

也许你想要一个下限。

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: Manifest[A])String;
 method f has incompatible type
       object Impl extends Service { def f[A >: SuperBase : Manifest](g: A => Unit) = manifest[A].runtimeClass.toString }
                                         ^

请注意,您的代码是这样说的:

方法 ServeImp.doWork 必须接受 "a function that must accept some class T that is a sublass of Base and Superbase"

的参数

SuperBaseImpl 不是 Base 的子class,但这不是错误,因为可能存在满足该要求的 class X "extends SuperBaseImpl with Base"。

当发生类型推断时,T 被解析为 "foo.Base with foo.SuperBaseImpl",满足上述所有要求。 runtimeClass 是 interface 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 

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

已添加:

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

trait A
trait B

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

m((x: B) => Unit)
//res0: String = A with B