为什么在这里Scala类型推断失败了?

11

我在 Scala 中有这个类:

object Util {
  class Tapper[A](tapMe: A) {
    def tap(f: A => Unit): A = {
      f(tapMe)
      tapMe
    }

    def tap(fs: (A => Unit)*): A = {
      fs.foreach(_(tapMe))
      tapMe
    }
  }

  implicit def tapper[A](toTap: A): Tapper[A] = new Tapper(toTap)
}

现在,

"aaa".tap(_.trim)

代码无法编译,出现以下错误:

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

为什么类型不能被推断为String?从错误信息来看,隐式转换确实起作用了(否则错误信息应该是类似于"tap不是String类的成员")。而且似乎转换必须是Tapper[String]类型,这意味着参数的类型是String => Unit(或(String => Unit)*)。

有趣的是,如果我注释掉任意一个tap定义,那么它就可以编译通过。

1个回答

17

6.26.3 函数重载解析

首先根据参数的形状确定可能适用的函数集合。

...

如果B中恰好有一种可选方案,则选择该方案。

否则,让S1,...,Sm是通过将每个参数与未定义的预期类型进行类型化而获得的类型向量。

基于参数的“形状”(考虑到元数和类型构造函数FunctionN),tap的两个重载都可能适用。

因此,类型推导器会像处理以下情况一样进行处理:

val x = _.trim

然而它失败了。

一个更聪明的算法可以取每个选择的相应参数类型的上界,并将其用作期望类型。但是在我看来,这种复杂度并不值得。重载有许多边角情况,这只是其中之一。

但是在这种情况下,有一个技巧可以使用,如果你真的需要一个接受单个参数的重载:

object Util {
  class Tapper[A](tapMe: A) {
    def tap(f: A => Unit): A = {
      f(tapMe)
      tapMe
    }

    def tap(f0: A => Unit, f1: A => Unit, fs: (A => Unit)*): A = {
      (Seq(f0, f1) ++ fs).foreach(_(tapMe))
      tapMe
    }
  }

  implicit def tapper[A](toTap: A): Tapper[A] = new Tapper(toTap)

  "".tap(_.toString)
  "".tap(_.toString, _.toString)
  "".tap(_.toString, _.toString, _.toString)
}

太好了,谢谢!我原本以为我必须给它们取不同的名称。 - Alexey Romanov
3
杰森,你正在迅速成为新的丹尼尔! - oxbow_lakes
2
@oxbow更好的是,他经常引用规范,这是一件好事。 - Daniel C. Sobral
2
如果这个规范有一个 Stack Overflow 账户就好了,他知道所有的答案! - retronym

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接