类型擦除导致扩展函数缺少参数类型
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 为你的 A
和 B
附加类型信息,以便在 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)
类型。相反,您可以让编译器有机会自行解决这个问题,如果我正确理解这里发生的事情 - 编译器可以确定您在这里不需要 ClassTag
s,因为它们什么都不做。看到这个:
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))(_ + _))
这不会产生任何警告。
我写了下面的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 为你的 A
和 B
附加类型信息,以便在 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)
类型。相反,您可以让编译器有机会自行解决这个问题,如果我正确理解这里发生的事情 - 编译器可以确定您在这里不需要 ClassTag
s,因为它们什么都不做。看到这个:
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))(_ + _))
这不会产生任何警告。