为什么不推断通用函数的参数类型?

Jac*_*cki 7 generics scala

在Scala 2.11.7中,具有以下案例类和其他apply方法:

case class FieldValidator[T](key: String, isValid: T => Boolean, 
                             errorMessage: Option[String] = None)

object FieldValidator {
  def apply[T](key: String, isValid: T => Boolean, 
               errorMessage: String): FieldValidator[T] = ???
}
Run Code Online (Sandbox Code Playgroud)

当我尝试使用时:

FieldValidator[String](key, v => !required || v.nonEmpty, "xxx")
Run Code Online (Sandbox Code Playgroud)

我收到一个"缺少参数类型"编译错误指向v.

当我明确指定类型时v,它编译得很好,我甚至可以跳过方法的泛型类型apply,即

FieldValidator(key, (v: String) => !required || v.nonEmpty, "xxx")
Run Code Online (Sandbox Code Playgroud)

vapply提供通用类型时,为什么不推断类型?

And*_*kin 3

这与泛型无关,而是重载和默认参数的问题。

首先,回想一下,sinceFieldValidator是一个case类,一个合成工厂方法

def apply(
  key: String,
  isValid: T => Boolean, 
  errorMessage: Option[String] = None
)
Run Code Online (Sandbox Code Playgroud)

会自动添加到伴生对象中FieldValidator。这导致Field验证器具有两个具有默认参数和相同名称的泛型方法。

这是一个较短的示例,其行为方式大致相同:

def foo[A](f: A => Boolean, x: Int = 0): Unit =  {}
def foo[A](f: A => Boolean, x: String): Unit =  {}

foo[String](_.isEmpty)
Run Code Online (Sandbox Code Playgroud)

其结果是:

错误:扩展函数缺少参数类型 ((x$1: ) => x$1.isEmpty)

 foo[String](_.isEmpty)
             ^
Run Code Online (Sandbox Code Playgroud)

我无法确定到底出了什么问题,但本质上,您通过向编译器抛出三种不同类型的多态性,使编译器陷入了太多的歧义:

  • 重载:你有两个具有名称的方法apply
  • 泛型:你的方法有一个泛型类型参数[A]
  • 默认参数:您的errorMessagex在我的较短示例中)可以省略。

总之,这使得编译器可以在两个名称相同、类型不明确且预期类型参数数量不明确的方法之间进行选择。虽然灵活性很好,但太多的灵活性就太多了,编译器会放弃尝试找出您想要的内容,并强制您显式指定每个参数的所有类型,而不依赖于推理。

从理论上讲,它可以在这种特殊情况下找到答案,但这将需要更复杂的推理算法以及更多的回溯和试错(这会减慢一般情况下的编译速度)。你不希望编译器花半天时间玩类型系统数独,即使它理论上可以找出唯一的解决方案。快速退出并显示错误消息是一个合理的选择。


解决方法

作为一种简单的解决方法,请考虑以允许编译器尽快消除歧义的方式对参数进行重新排序。例如,将参数分成两个参数列表,其中两个String在前,将使其明确:

case class FieldValidator[T](
  key: String,
  isValid: T => Boolean, 
  errorMessage: Option[String] = None
)

object FieldValidator {
  def apply[T]
    (key: String, errorMessage: String)
    (isValid: T => Boolean)
  : FieldValidator[T] = {
    ???
  }
}

val f = FieldValidator[String]("key", "err"){
  s => s.nonEmpty
}
Run Code Online (Sandbox Code Playgroud)