1

我正在阅读 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)
}

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

找不到 Example.Serializer[Any] 类型的证据参数的隐式值

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

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

任何解释将不胜感激!

4

1 回答 1

4

问题是推断的类型datais List[List[Any]],因此当您调用 时f1,推断的类型T0is 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]

在我看来,无论如何,使用这样的隐式转换并不是一个好主意,即使它们由类型类提供支持。

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

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

这会编译(带有警告),但ClassCastException会在运行时崩溃。

至少可以说类型擦除是一件好事,因为未擦除的类型破坏了参数性,而 Scala 中唯一的问题是它的类型擦除不是更完整。在这种情况下,唯一的问题broken是完全匹配运行时类型的能力——而不是它不适用于泛型类型。

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

于 2020-06-21T17:43:10.037 回答