首页 文章

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

提问于
浏览
15

假设我有一个名为 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的简化版本 .

2 回答

  • 4

    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
    

    它's usually bad to recommend such implicit conversions, but seems fine here (if you won'吨过度使用 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")
                                           ^
    
  • 6

    代码由于逆转而编译 . 您可以通过显式提供推断类型参数来查看:

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

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

相关问题