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

时间:2015-11-12 17:44:04

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

修改

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

s.doWork[Base with SuperBaseImpl]

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

3 个答案:

答案 0 :(得分: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 }
                                         ^

答案 1 :(得分: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

答案 2 :(得分:1)

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

方法ServeImp.doWork必须接受一个参数,即&#34;一个必须接受某个类T的函数,它是Base和Superbase的子类&#34;

SuperBaseImpl不是Base的子类,但它不是一个错误,因为可能存在一个类X,其中&#34;扩展SuperBaseImpl与Base&#34;这将满足这一要求。

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