Scala的类型删除如何适用于更高版本的类型参数? [英] How does Scala's type erasure work for higher kinded type parameters?
问题描述
我不明白哪个泛型类型参数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
到功能
。
问题:
- 我的理解是正确的,
CastTo
的参数没有被清除,或者还有别的东西我没有看到?一些隐式操作或任何东西? - 是否有描述此行为的任何文档?
- 是否有任何理由要我 这种行为?我发现它有点反直觉,但也许这只是我,我使用的工具是错误的...
感谢阅读。
编辑:在类似的例子中发现了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]
至于其他问题:
- 不是我立即知道的。
- 为什么你不想要 它?您正在将
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 aboutFoo
, this is expected. - The case 2 indicates that
asInstanceOf[Foo[X,Y]]
does not care aboutX
andY
, this is also expected. - The case 5 indicates that
asInstanceOf
does not care about higher kinded type parameterMap
, 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):
- 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? - Is there any documentation that describes this behavior?
- 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:
- Not that I immediately know of.
- Why would you not want it? You are casting a
String
to aMap[Int, Long]
. That is bound to crash eventually. Failing (relatively) fast with aClassCastException
is probably the safest, most user-friendly option.
这篇关于Scala的类型删除如何适用于更高版本的类型参数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!