具有上限的函数中类型推断的奇怪行为
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
运行 在实现中更改上限时进入这种奇怪的行为,但忘记在界面中更改它。我认为最后一条语句不应该编译,但它确实编译了 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