在具有给定上下文绑定的嵌套列表中键入擦除

Type erasure in a nested list with a given context bound

我正在阅读 Scala with Cats 这本书。我试图了解 scala 类型系统的微妙之处。我想到了以下示例:

object Example extends App {

  sealed trait Serializer[T] {
    def serialize(seq: List[T]): String
  }

  implicit object StringSerializer extends Serializer[String] {
    def serialize(seq: List[String]): String = seq.toString()
  }

  implicit object IntSerializer extends Serializer[Int] {
    def serialize(seq: List[Int]): String =  seq.toString()
  }

  def f1[T0 : Serializer](x: List[List[T0]])(implicit s0: Serializer[T0]): List[String] = {
    x.map(lst => s0.serialize(lst))
  }
  
  // some dummy data
  val col1 = List("a", "b", "c", "d", "e")
  val col2 = List(12, 200, 80900, 201200, 124420000)
  val col3 = List(121, 12121, 71240000, 44356, 845)
  val data = List(col1, col2, col3)
  
  f1(data)
}

现在编译不通过,出现以下错误:

could not find implicit value for evidence parameter of type Example.Serializer[Any]

现在我明白为什么会这样了;这是由于我的函数 f1。因为我有一个 List[Int] 和 List[String] 传递给函数,所以公共父类型是 Any。所以类型信息被删除,传递给序列化器。

但是,鉴于我已经设置了上下文绑定,编译器难道不应该在这发生之前首先查找隐式定义吗?显然不是,所以我的理解是不正确的。 Scala 解决这个问题的方法是什么。

任何解释将不胜感激!

问题是 data 的推断类型是 List[List[Any]],因此当您调用 f1 时,T0 的推断类型是 Any,它没有 Serializer 实例。即使您不将 data 定义为 val,而是编写类似 f1(List(col1, col2, col3)) 的内容,T0 的推断类型仍将是 Any.

Scala 并没有真正提供任何方法让您可以做您想要做的事情。最接近的解决方案可能类似于磁铁图案——例如,您可以添加如下内容:

trait SerializableList {
  type T
  def values: List[T]
  def instance: Serializer[T]
  final def apply(): String = instance.serialize(values)
}

object SerializableList {
  implicit def fromSerializer[T0](ts: List[T0])
    (implicit T: Serializer[T0]): SerializableList =
      new SerializableList {
        type T = T0
        val values = ts
        val instance = T
      }
}

然后像这样定义f1

def f1(x: List[SerializableList]): List[String] = {
  x.map(_())
}

这实际上适用于您的情况,前提是您传递了一个尚未推断出元素类型的表达式:

scala> f1(List(col1, col2, col3))
res3: List[String] = List(List(a, b, c, d, e), List(12, 200, 80900, 201200, 124420000), List(121, 12121, 71240000, 44356, 845))

但是如果你尝试 f1(data) 它仍然不会工作,因为 data 的静态类型已经是 List[List[Any]]:

scala> f1(data)
          ^
       error: type mismatch;
        found   : List[List[Any]]
        required: List[SerializableList]

在我看来,使用这样的隐式转换并不是一个好主意,无论如何,即使它们由 class.

类型提供支持

作为脚注,您所看到的与 type erasure 没有任何关系,在 Scala 和 Java 中,它是关于失去对运行时反射中的泛型类型。例如,这是类型擦除如何在 Scala 中启用不安全程序的示例:

def broken() = List(1, 2, 3) match { case xs: List[String] => xs.head }

这可以编译(带有警告),但会在运行时崩溃并显示 ClassCastException

至少可以说类型擦除是一件好事,因为未擦除的类型会破坏参数化,并且 Scala 中唯一的问题是它的类型擦除不是更完整。在这个观点中,broken 的唯一问题是完全匹配运行时类型的能力——而不是它不适用于泛型类型的事实。

在您的情况下,没有运行时反射,并且在推断出 Any 时您丢失了特定类型信息这一事实并不是 erasure,至少在该术语通常在这种情况下使用的意义。相反,它是 最小上限 被采用的问题。