在给定上下文绑定的嵌套列表中键入擦除 [英] Type erasure in a nested list with a given context bound

查看:100
本文介绍了在给定上下文绑定的嵌套列表中键入擦除的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在读《斯卡拉与猫》一书.我试图了解scala类型系统的精妙之处.我想出了以下示例:

I am going through the book Scala with Cats. I am trying to understand the subtleties of the scala type system. I came up with the following example:

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)
}

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

Now this does not compile, the following error comes up:

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

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

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

Now I understand why this happens; it is due to my function f1. Because I have a List[Int] and List[String] passed in to the function, the common parent type is Any. So the Type info gets erased, which passes into serializer.

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

However, given I have put a context bound, shouldn't the compiler first look for the implicit definitons before this takes place? Clearly it does not so my understanding is incorrect. What is the Scala way of getting round this problem.

任何解释将不胜感激!

推荐答案

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

The issue is that the inferred type of data is List[List[Any]], so that when you call f1, the type that's inferred for T0 is Any, which doesn't have a Serializer instance. Even if you don't define data as a val, and instead write something like f1(List(col1, col2, col3)), the inferred type of T0 will still be Any.

Scala并没有真正提供您可以实现目标的任何方式.最接近的解决方案可能类似于磁铁图案-例如,您可以添加以下内容:

Scala just doesn't really provide any way that you can do the kind of thing you're aiming for. The closest solution is probably something like the magnet pattern—for example you could add something like this:

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(_())
}

这实际上适用于您的情况,只要您传递一个尚未推断出元素类型的表达式即可:

And this actually works for your case, provided that you pass an expression where the element types haven't been inferred yet:

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]]:

But if you try f1(data) it still won't work, since the static type of data is already List[List[Any]]:

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

在我看来,即使这样使用隐式转换,即使它们由类型类提供动力,也不是一个好主意.

In my view it isn't really a good idea to use implicit conversions like this, anyway, though, even if they're powered by a type class.

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

As a footnote, what you're seeing doesn't really have anything to do with type erasure, which in Scala and Java is about losing access to generic types in runtime reflection. For example, this is an example of how type erasure can enable unsafe programs in Scala:

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

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

This compiles (with a warning), but crashes with a ClassCastException at runtime.

至少可以争论的是,类型擦除是一件好事,因为未擦除的类型会破坏参数性,而Scala唯一的问题是其类型擦除还不完整.在此视图中,broken的唯一问题是完全能够在运行时类型上进行匹配,而不是它不适用于泛型类型的事实.

It's at least arguable that type erasure is a good thing, since unerased types undermine parametricity, and that the only problem in Scala is that its type erasure isn't more complete. The only issue with broken, in this view, is the ability to match on runtime type at all—not the fact that it doesn't work for generic types.

在您的情况下,没有运行时反映,并且在推断Any时丢失了特定类型信息的事实不是 erasure ,至少在某种意义上来说通常是这样在这种情况下.取而代之的是最小上限.

In your case there's no runtime reflection, and the fact that you've lost specific type information when Any is inferred isn't erasure, at least in the sense that term is typically used in this context. Instead it's a matter of the least upper bound being taken.

这篇关于在给定上下文绑定的嵌套列表中键入擦除的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆