当参数不符合类型约束时,为什么这个 Scala 函数可以编译?

Why does this Scala function compile when the argument does not conform to the type constraint?

假设我有一个名为 Marker 的空标记特征和一些类型参数由 Marker:

绑定的函数
trait Marker

object Marker {
  def works[M <: Marker](m:M):M = m
  def doesntWork[M <: Marker](f:M => String):String = "doesn't matter"
}

第一个功能如我所料。也就是说,如果您传递的参数不是 Marker,则代码不会编译:

scala> works("a string")
<console>:14: error: inferred type arguments [String] do not conform to method works's type parameter bounds [M <: com.joescii.Marker]
       works("a string")
       ^
<console>:14: error: type mismatch;
 found   : String("a string")
 required: M
       works("a string")
             ^

但是,我可以将参数传递给不符合 Marker 的第二个函数。具体来说,我可以传递一个类型为 String => String 的函数,并且代码可以愉快地编译并运行:

scala> doesntWork( (str:String) => "a string" )
res1: String = doesn't matter

我希望对 doesntWork 的调用无法编译。任何人都可以向我解释它为什么编译以及我如何更改函数签名以防止在这种情况下检查类型?

完全披露:上面的人为示例是 this outstanding issue for lift-ng.

的简化版本

M => String其实就是一个Function1[M, String]。如果您查看定义:

 trait Function1[-T1, +R]

所以M变成了逆变,这意味着对于M1 >: M2Function1[M1, String] <: Function1[M2, String],假设M1 = Any然后Function1[Any, String] <: Function1[Marker, String]

并且 doesntWork - f 的输入也是逆变的,这意味着您可以传递小于 M => String 的值,正如我刚才展示的那样,Any => String 小于 Marker => String,因此它完全可以通过。

你也可以传递 String => String 因为你的 [M <: Marker],这最终导致编译器将 M 解释为 Nothing,所以即使 String => String 变得更大比 M => String.


要解决你的问题,只需引入包装器,这将使你的类型不变:

scala> case class F[M](f: M => String)
defined class F

scala> def doesntWork[M <: Marker](f:F[M]):String = "doesn't matter"
doesntWork: [M <: Marker](f: F[M])String

scala> doesntWork(F((str: String) => "a string"))
<console>:18: error: inferred type arguments [String] do not conform to method doesntWork's type parameter bounds [M <: Marker]
              doesntWork(F((str: String) => "a string"))
              ^
<console>:18: error: type mismatch;
 found   : F[String]
 required: F[M]
              doesntWork(F((str: String) => "a string"))
                          ^
scala> doesntWork(F((str: Any) => "a string"))
<console>:18: error: inferred type arguments [Any] do not conform to method doesntWork's type parameter bounds [M <: Marker]
              doesntWork(F((str: Any) => "a string"))
              ^
<console>:18: error: type mismatch;
 found   : F[Any]
 required: F[M]
Note: Any >: M, but class F is invariant in type M.
You may wish to define M as -M instead. (SLS 4.5)
              doesntWork(F((str: Any) => "a string"))

scala> doesntWork(F((str: Marker) => "a string"))
res21: String = doesn't matter

scala> trait Marker2 extends Marker
defined trait Marker2

scala> doesntWork(F((str: Marker) => "a string"))
res22: String = doesn't matter

scala> doesntWork(F((str: Marker2) => "a string"))
res23: String = doesn't matter

推荐这种隐式转换通常是不好的,但在这里似乎很好(如果你不会过度使用 F):

scala> implicit def wrap[M](f: M => String) = F(f)
warning: there was one feature warning; re-run with -feature for details
wrap: [M](f: M => String)F[M]

scala> doesntWork((str: Marker) => "a string")
res27: String = doesn't matter

scala> doesntWork((str: String) => "a string")
<console>:21: error: inferred type arguments [String] do not conform to method doesntWork's type parameter bounds [M <: Marker]
              doesntWork((str: String) => "a string")
              ^
<console>:21: error: type mismatch;
 found   : F[String]
 required: F[M]
              doesntWork((str: String) => "a string")
                                       ^

代码编译是因为逆变。您可以通过显式给出推断类型参数来看到:

doesntWork[Nothing]((str: String) => "a string")

这是一个普遍的问题。有多种技术可以解决这个问题,但它们通常归结为将 T 限制为某种类型的实例 class.