Scala的类型删除如何适用于更高版本的类型参数? [英] How does Scala's type erasure work for higher kinded type parameters?

查看:650
本文介绍了Scala的类型删除如何适用于更高版本的类型参数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我不明白哪个泛型类型参数Scala会删除。我曾经认为它应该清除所有泛型类型参数,但这似乎不是这种情况。



如果我' m错误:如果我在代码中实例化类型为 Map [Int,String] 的实例,那么在运行时,实例只知道它的类型为 Map [_,_] ,并且不知道其泛型类型参数。这是为什么以下成功编译和执行没有错误:

  val x:Map [Int,String] = Map( 2  - >a)
val y:Map [String,Int] = x.asInstanceOf [Map [String,Int]]

现在我预计所有更高级的类型参数也会被删除,也就是说,如果我有一个

  class Foo [H [_,_],X,Y] 

我期望 Foo [Map,Int,String] 类型的实例对 Map 没有任何意见。但是现在考虑下面这些类型转换的实验:

  import scala.language.higherKinds 

def cast1 [A](a:Any):A = a.asInstanceOf [A]
def cast2 [H [_,_],A,B](a:Any)= a.asInstanceOf [H [A,B]]
def cast3 [F [_ [_,_],_,_],H [_,_],X,Y](a:Any)= a.asInstanceOf [F [ H,X,Y]]
类CastTo [H [_,_],A,B] {
def cast(x:Any):H [A,B] = x.asInstanceOf [H [A,B]]
}

ignoreException {
val v1 = cast1 [String](List [Int](1,2,3))
/抛出ClassCastException

$ b ignoreException {
val v2 = cast2 [Map,Int,Long](Map [String,Double](a - > 1.0))
//完全不抱怨,`A`和`B`擦除
}

ignoreException {
//正确类型
val v3 = cast2 [Map,Int,Long]((x:Int)=> x.toLong)
//抛出ClassCastException
}

ignoreException {
/ /错误的种类
val v4 = cast2 [Map,Int,Long] (错误的种类)
//抛出ClassCastException
}

ignoreException {
class Foo [H [_,_],X,Y](h: H [X,Y])
val x = new Foo [Function,Int,String](n => #* n)
val v5 = cast3 [Foo,Map,Int,Long](x)
//没有任何反应,用`Map`快乐地替换`Function`
}

ignoreException {
val v6 =(new CastTo [Map,Int,Long])。cast(List(hello?))
// throws ClassCastException
}

ignoreException {
val castToMap = new CastTo [Map,Int,Long]
val v7 = castToMap.cast(它怎么检测这个?)
//抛出ClassCastException
}

ignoreException {
val castToMap = new CastTo [Map,Int,Long]
val madCast = castToMap.asInstanceOf [CastTo [Function ,Float,Double]]
val v8 = madCast.cast(它检测到什么?)
//字符串不能转换为函数???
//为什么在这里保留关于`Function`的任何信息?
}


// ------------------------------- -------------------------------------
var ignoreBlockCounter = 0
/ **执行一段代码,
*捕获一个exeption(如果抛出),
*打印`ignoreException`包装块的数量,
*打印异常的名称。
* /
def ignoreException [U](f:=> U):Unit = {
ignoreBlockCounter + = 1
try {
f
} catch {
case e:Exception =>
println([+ ignoreBlockCounter +]+ e)
}
}

这是输出(scala -version 2.12.4):

  [1] java.lang .ClassCastException:scala.collection.immutable。$ colon $冒号不能转换为java.lang.String 
[3] java.lang.ClassCastException:Main $$ anon $ 1 $$ Lambda $ 143/1744347043无法转换为scala.collection.immutable.Map
[4] java.lang.ClassCastException:java.lang.String不能转换为scala.collection.immutable.Map
[6] java.lang.ClassCastException:scala .collection.immutable。$ colon $冒号不能转换为scala.collection.immutable.Map
[7] java.lang.ClassCastException:java.lang.String不能转换为scala.collection.immutable.Map
[8] java.lang.ClassCastException:java.lang.String不能转换为scala.Function1




  • 案例1,3,4表明 asInstanceOf [Foo [...]] 确实关心 Foo ,这是预期的。

  • 案例2表明 asInstanceOf [Foo [X,Y]] 没有 关心 X Y ,这也是预料之中的。

  • 案例5表示 asInstanceOf does not 关心更高的亲属类型参数 Map ,类似于情况2,这也是预期的。



到目前为止这么好。但是,案例6,7,8提出了一个不同的行为:这里,类型为 CastTo [Foo,X,Y] 的实例似乎保留了有关泛型类型参数的信息某些原因, Foo 。更确切地说,一个 CastTo [Map,Int,Long] 似乎带有足够的信息以知道一个字符串不能被转换为 Map 。此外,在情况8中,似乎甚至因为剧组改变 Map 功能

问题:


  1. 我的理解是正确的, CastTo 的参数没有被清除,或者还有别的东西我没有看到?一些隐式操作或任何东西?

  2. 是否有描述此行为的任何文档?
  3. 是否有任何理由要我 这种行为?我发现它有点反直觉,但也许这只是我,我使用的工具是错误的...

感谢阅读。



编辑:在类似的例子中发现了2.12.4编译器的问题(请参阅我自己的答案 ,但这是一个单独的问题。

解决方案

我认为你对一些事情感到困惑。 b $ b

强制转换为泛型类型直到类型变为具体。例如,拿这段代码:

 类CastTo [H [_,_],A,B] {
def cast(x:Any):H [A,B] = x.asInstanceOf [H [A,B]]
}

在字节码中,你只能投射到一个真实的类,因为它不知道泛型。所以上面的代码在字节码中大致相当于:

  class CastTo {
def cast(x:Object ):Object = x
}

然后在代码中给出一个 String 给方法 cast ,编译器可以根据它的类型信息看到一个 Map [ Int,Long] 会出来。但是在字节码中,cast 的擦除返回类型为 Object ,所以编译器必须在 use-site cast 方法。此代码

  val castToMap = new CastTo [Map,Int,Long] 
val v7 = castToMap.cast(它是如何检测到这个的?)

在字节码中大致等价于以下伪代码):

  val castToMap = new CastTo 
val v7 = castToMap.cast(它怎么能检测到这个?)。asInstanceOf [Map]

至于其他问题:


  1. 不是我立即知道的。

  2. 为什么你不想要 它?您正在将 String 转换为 Map [Int,Long] 。这最终肯定会崩溃。 ( ClassCastException )失败(相对)速度可能是最安全,最用户友好的选择。


I don't understand which generic type parameters Scala erases. I used to think that it should erase all generic type parameters, but this does not seem to be the case.

Correct me if I'm wrong: if I instantiate an instance of type Map[Int, String] in the code, then at the runtime, the instance knows only that it is of type Map[_, _], and does not know anything about its generic type parameters. This is why the following succeeds to compile and to execute without errors:

val x: Map[Int, String] = Map(2 -> "a")
val y: Map[String, Int] = x.asInstanceOf[Map[String, Int]]

Now I would expect that all higher-kinded type parameters are also erased, that is, if I have a

class Foo[H[_, _], X, Y]

I would expect that an instance of type Foo[Map, Int, String] knows nothing about Map. But now consider the following sequence of type-cast "experiments":

import scala.language.higherKinds

def cast1[A](a: Any): A = a.asInstanceOf[A]
def cast2[H[_, _], A, B](a: Any) = a.asInstanceOf[H[A, B]]
def cast3[F[_[_, _], _, _], H[_, _], X, Y](a: Any) = a.asInstanceOf[F[H, X, Y]]
class CastTo[H[_, _], A, B] {
  def cast(x: Any): H[A, B] = x.asInstanceOf[H[A, B]]
}

ignoreException {
  val v1 = cast1[String](List[Int](1, 2, 3))                      
  // throws ClassCastException
}

ignoreException {
  val v2 = cast2[Map, Int, Long](Map[String, Double]("a" -> 1.0)) 
  // doesn't complain at all, `A` and `B` erased
}

ignoreException {
  // right kind
  val v3 = cast2[Map, Int, Long]((x: Int) => x.toLong)            
  // throws ClassCastException
}

ignoreException {
  // wrong kind
  val v4 = cast2[Map, Int, Long]("wrong kind")                     
  // throws ClassCastException
}

ignoreException {
  class Foo[H[_, _], X, Y](h: H[X, Y])
  val x = new Foo[Function, Int, String](n => "#" * n)
  val v5 = cast3[Foo, Map, Int, Long](x)
  // nothing happens, happily replaces `Function` by `Map`
}

ignoreException {
  val v6 = (new CastTo[Map, Int, Long]).cast(List("hello?"))       
  // throws ClassCastException
}

ignoreException {
  val castToMap = new CastTo[Map, Int, Long]
  val v7 = castToMap.cast("how can it detect this?")               
  // throws ClassCastException
}

ignoreException {
  val castToMap = new CastTo[Map, Int, Long]
  val madCast = castToMap.asInstanceOf[CastTo[Function, Float, Double]]
  val v8 = madCast.cast("what does it detect at all?")
  // String cannot be cast to Function???
  // Why does it retain any information about `Function` here?
}


// --------------------------------------------------------------------
var ignoreBlockCounter = 0
/** Executes piece of code, 
  * catches an exeption (if one is thrown),
  * prints number of `ignoreException`-wrapped block,
  * prints name of the exception.
  */
def ignoreException[U](f: => U): Unit = {
  ignoreBlockCounter += 1
  try {
    f
  } catch {
    case e: Exception =>
      println("[" + ignoreBlockCounter + "]" + e)
  }
}

Here is the output (scala -version 2.12.4):

[1]java.lang.ClassCastException: scala.collection.immutable.$colon$colon cannot be cast to java.lang.String
[3]java.lang.ClassCastException: Main$$anon$1$$Lambda$143/1744347043 cannot be cast to scala.collection.immutable.Map
[4]java.lang.ClassCastException: java.lang.String cannot be cast to scala.collection.immutable.Map
[6]java.lang.ClassCastException: scala.collection.immutable.$colon$colon cannot be cast to scala.collection.immutable.Map
[7]java.lang.ClassCastException: java.lang.String cannot be cast to scala.collection.immutable.Map
[8]java.lang.ClassCastException: java.lang.String cannot be cast to scala.Function1

  • The cases 1, 3, 4 indicate that asInstanceOf[Foo[...]] does care about Foo, this is expected.
  • The case 2 indicates that asInstanceOf[Foo[X,Y]] does not care about X and Y, this is also expected.
  • The case 5 indicates that asInstanceOf does not care about higher kinded type parameter Map, similar to case 2, this is also expected.

So far so good. However, the cases 6, 7, 8 suggest a different behavior: here, an instance of type CastTo[Foo, X, Y] seems to retain information about the generic type parameter Foo for some reason. More precisely, a CastTo[Map, Int, Long] seems to carry around enough information with it to know that a string cannot be cast into a Map. Moreover, in case 8, it seems to even change Map to Function because of a cast.

Question(s):

  1. Is my understanding correct that the first generic parameter of CastTo is not erased, or is there something else what I don't see? Some implicit operation or anything?
  2. Is there any documentation that describes this behavior?
  3. Is there any reason why I should want this behavior? I find it somewhat counter-intuitive, but maybe it's just me, and I'm using the tool wrong...

Thanks for reading.

EDIT: Poking around in similar examples revealed an issue with the 2.12.4-compiler (see my own "answer" below), but this is a separate issue.

解决方案

I think you are confusing some things.

Casts to generic types are deferred until the point where the types become concrete. For example, take this piece of code:

class CastTo[H[_, _], A, B] {
  def cast(x: Any): H[A, B] = x.asInstanceOf[H[A, B]]
}

In the bytecode you can only cast to a real class, because it doesn't know anything about generics. So the above will, in bytecode, be roughly equivalent to:

class CastTo {
  def cast(x: Object): Object = x
}

Then later in the code you give a String to method cast and the compiler can see that according to the type information it has, a Map[Int, Long] will come out. But in the bytecode cast has an erased return type of Object, so the compiler has to insert a cast at the use-site of the cast method. This code

val castToMap = new CastTo[Map, Int, Long]
val v7 = castToMap.cast("how can it detect this?")

will, in the bytecode, be roughly equivalent to the following (pseudo) code:

val castToMap = new CastTo
val v7 = castToMap.cast("how can it detect this?").asInstanceOf[Map]

As for your other questions:

  1. Not that I immediately know of.
  2. Why would you not want it? You are casting a String to a Map[Int, Long]. That is bound to crash eventually. Failing (relatively) fast with a ClassCastException is probably the safest, most user-friendly option.

这篇关于Scala的类型删除如何适用于更高版本的类型参数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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