【问题标题】:Why is the parameter type of a generic function not inferred?为什么没有推断出泛型函数的参数类型?
【发布时间】:2019-01-24 23:04:46
【问题描述】:

在 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] = ???
}

当我尝试使用时:

FieldValidator[String](key, v => !required || v.nonEmpty, "xxx")

我收到指向 v 的“缺少参数类型”编译错误。

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

FieldValidator(key, (v: String) => !required || v.nonEmpty, "xxx")

为什么只提供apply 的泛型类型时不推断v 的类型?

【问题讨论】:

  • @RamanMishra 我不同意提议的副本,它只是表面上相似。这个特定问题的有趣之处在于,当您删除case class FieldValidator 并只留下object FieldValidator 时,它实际上会编译。用户定义的apply 和合成方法之间似乎存在某种冲突。
  • 是的,我同意@AndreyTyukin

标签: scala generics


【解决方案1】:

与其说是泛型,不如说是重载和默认参数的问题。

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

def apply(
  key: String,
  isValid: T => Boolean, 
  errorMessage: Option[String] = None
)

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

下面是一个简短的示例,其行为方式大致相同:

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

foo[String](_.isEmpty)

结果:

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

 foo[String](_.isEmpty)
             ^

我无法确定 究竟是什么出了问题,但本质上,您通过向编译器抛出三种不同类型的多态性将编译器与太多歧义混淆了:

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

总而言之,这使编译器可以在两个同名的方法之间进行选择,这些方法具有不明确的类型和不明确的预期类型参数数量。虽然灵活性很好,但太多灵活性只是太多,编译器放弃尝试找出你想要的东西,并强制你指定每一个的所有类型明确论证,不依赖推理。

理论上,它可以在这种特殊情况下解决这个问题,但这需要更复杂的推理算法和更多的回溯和反复试验(这会减慢一般情况下的编译速度)。您不希望编译器花半天时间玩 typesystem-sudoku,即使它理论上可以找出一个独特的解决方案。快速退出并显示错误消息是一种合理的选择。


解决方法

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

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
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-02-04
    • 1970-01-01
    • 2020-08-31
    • 1970-01-01
    • 1970-01-01
    • 2016-11-01
    • 1970-01-01
    相关资源
    最近更新 更多