类型擦除导致扩展函数缺少参数类型

Type erasure leads to missing parameter type for expanded function

我写了下面的class,以后要按照Pimp My Library模式使用:

class ListX[A] (list: List[A]) {

    def zipMap[A, B, C] (that: List[B], op: (A, B) => C): List[C] =
        list.zip(that).map({
            case (a: A, b: B) => op(a, b)
        })
}

编译时出现警告:

[warn] /src/main/scala/ListX.scala:8: abstract type pattern A is unchecked since it is eliminated by erasure
[warn]             case (a: A, b: B) => op(a, b)
[warn]                      ^
[warn] /src/main/scala/ListX.scala:8: abstract type pattern B is unchecked since it is eliminated by erasure
[warn]             case (a: A, b: B) => op(a, b)
[warn]                            ^
[warn] two warnings found

在 REPL 中测试它会导致以下错误:

scala> val a = new ListX(List(1, 1, 1))

scala> val b = List(1, 1, 1)

scala> val result = a.zipMap(b, _ + _)
error: missing parameter type for expanded function ((x: <error>, x) => x.$plus(x))

scala> val expected = List(2, 2, 2)

由于我是 Scala 的新手,所以我不完全理解警告和错误。我知道有一个叫做 "type erasure" 的东西,但不知道它是如何工作的,我可以看到这可能会导致缺少类型。

那么哪里出了问题,我该如何纠正?

更新:

感谢接受的答案及其评论,我设法解决了问题并重写了 class 如下:

implicit class ListX[A] (list: List[A]) {

    def zipMap[B, C] (that: List[B])(op: (A, B) => C): List[C]
    = list.zip(that).map({ op.tupled })
}

你的代码有很多问题,不仅仅是类型擦除问题:

为了让 scala 派生出你的 lambda 的类型,你需要把它放在一个单独的参数组中:(that: List[B])(op: (A, B) => C) 然后像 zipMap(List(1, 1, 1))(_ + _) 那样调用它。你可以在这里找到很多关于这个的答案。简而言之,这是因为 scala 不知道您定义它的组中的类型 B,因此您指定类型的第一组无法推断类型

那么你定义了两次类型参数A。一个在您的 class 中,一个在您的方法中。你只需要一次。 Scala 将这两个 A 理解为两个不同的类型参数,因此后者 A 没有任何实际类型,因为它与 list: List[A] 没有关联,scala 无法为 _ + _.

并且您需要为类型参数绑定一个 ClassTag 上下文来对抗类型擦除。它被写成 [A : ClassTag] 简而言之,这允许 Scala 为你的 AB 附加类型信息,以便在 Scala 匹配中使用它。

所以结果代码是:

(请看答案末尾的代码是更好的版本,这个可以简化)

  class ListX[A : ClassTag](list: List[A]) {

    def zipMap[B: ClassTag, C](that: List[B])(op: (A, B) => C): List[C] =
      list.zip(that).map({
        case (a: A, b: B) => op(a, b)
      })
  }

  println(new ListX[Int](List(2, 2, 2)).zipMap(List(1, 1, 1))(_ + _))

更新

在仔细考虑@Łukasz 的评论后,我意识到我犯了一个错误:你真的不需要这里的 ClassTag。它生成警告的问题是您明确指定 case 应该具有 (A, B) 类型。相反,您可以让编译器有机会自行解决这个问题,如果我正确理解这里发生的事情 - 编译器可以确定您在这里不需要 ClassTags,因为它们什么都不做。看到这个:

  class ListX[A](list: List[A]) {

    def zipMap[B, C](that: List[B])(op: (A, B) => C): List[C] =
      list.zip(that).map({
        case (a, b) => op(a, b)
      })
  }

  println(new ListX[Int](List(2, 2, 2)).zipMap(List(1, 1, 1))(_ + _))

这不会产生任何警告。