7

在实现中更改上限时遇到这种奇怪的行为,但忘记在接口中更改它。我认为最后一条语句不应该编译,但它确实并返回了意外的结果。

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]

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

4

3 回答 3

3

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

s.doWork[Base with SuperBaseImpl]

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

也许您打算设置一个下限。

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$1: Manifest[A])String;
 method f has incompatible type
       object Impl extends Service { def f[A >: SuperBase : Manifest](g: A => Unit) = manifest[A].runtimeClass.toString }
                                         ^
于 2015-11-13T06:46:57.613 回答
1

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

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

我只是认为类型参数T在某种程度上是“无人居住的”。T该方法除了它的上限之外什么都不知道Base,因此你得到一个清单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
于 2015-11-12T21:05:38.540 回答
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 

在这里,我让 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
于 2015-11-18T11:44:58.033 回答