考虑以下示例:
case class C[T](x:T) {
def f(t:T) = println(t)
type ValueType = T
}
val list = List(1 -> C(2), "hello" -> C("goodbye"))
for ((a,b) <- list) {
b.f(a)
}
在这个例子中,我知道(运行时保证)a
的类型将是T
,而b
的类型C[T]
具有相同的T
b.f(a)
1}}。当然,编译器无法知道,因此我们在b.f(a.asInstanceOf[T])
中得到了输入错误。
告诉编译器这个调用没问题,我们需要做一个类型转换T
。遗憾的是,此处不知道b.f(a)
。所以我的问题是:如何重写b.asInstanceOf[C[Any]].f(a)
以便编译此代码?
我正在寻找一种不涉及复杂结构的解决方案(以保持代码可读),并且在我们不应该依赖代码擦除使其工作的意义上是“干净的”(请参阅下面的第一种方法)。
我有一些工作方法,但由于各种原因我发现它们不能令人满意。
b
这是有效的,并且具有合理的可读性,但它基于“谎言”。 C[Any]
不是x.asInstanceOf[X]
类型,我们没有得到运行时错误的唯一原因是因为我们依赖于JVM(类型擦除)的限制。当我们知道x
确实属于X
类型时,我认为仅使用 b.f(a.asInstanceOf[b.ValueType])
是一种好方式。
ValueType
这应该根据我对类型系统的理解而有效。我已将成员C
添加到类T
,以便能够显式引用类型参数Error:(9, 22) type mismatch;
found : b.ValueType
(which expands to) _1
required: _1
b.f(a.asInstanceOf[b.ValueType])
^
。但是,在这种方法中我们得到一个神秘的错误信息:
_1
为什么呢?它似乎抱怨我们期望类型_1
但是类型为ValueType
! (但即使这种方法有效,也仅限于我们可以将成员C
添加到C
的情况。如果for ((a,b) <- list.asInstanceOf[List[(T,C[T]) forSome {type T}]]) {
b.f(a)
}
是某些现有的库类,我们无法做到要么。)
asInstanceOf
这个有效,并且在语义上是正确的(即,在调用a,b
时我们不会“撒谎”)。限制是这有点难以理解。此外,它有点特定于当前情况:如果 val (a2,b2) = (a,b).asInstanceOf[(T,C[T]) forSome {type T}]
b2.f(a2)
不是来自同一个迭代器,那么我们在哪里可以应用此类型转换? (此代码还有一个副作用,对于Intelli / J IDEA 2016.2而言过于复杂,在编辑器中将其强调为错误。)
a2,b2
我原本希望这个工作正常,因为T
现在应该为同一个存在主义C[T]
添加T
和Error:(10, 9) type mismatch;
found : a2.type (with underlying type Any)
required: T
b2.f(a2)
^
类型。但是我们得到了一个编译错误:
b match {
case b : C[t] => b.f(a.asInstanceOf[t])
}
为什么呢? (除此之外,由于对的创建和破坏,该方法的缺点是会产生运行时成本(我认为)。)
doGet()
这很有效。但是使用匹配来包含代码会使代码的可读性降低。 (对于Intelli / J来说它也太复杂了。)
答案 0 :(得分:5)
最干净的解决方案是IMO,它是您在类型捕获模式匹配时找到的解决方案。您可以通过将模式直接集成到您的理解中来使其简洁,并且希望可读,如下所示:
for ((a, b: C[t]) <- list) {
b.f(a.asInstanceOf[t])
}
小提琴:http://www.scala-js-fiddle.com/gist/b9030033133ee94e8c18ad772f3461a0
如果您还没有理解,遗憾的是相应的模式分配不起作用:
val (c, d: C[t]) = (a, b)
d.f(c.asInstanceOf[t])
那是因为t
不再在第二行的范围内了。在这种情况下,您必须使用完整的模式匹配。
答案 1 :(得分:4)
也许我对你想要达到的目标感到困惑,但这会编译:
case class C[T](x:T) {
def f(t:T) = println(t)
type ValueType = T
}
type CP[T] = (T, C[T])
val list = List[CP[T forSome {type T}]](1 -> C(2), "hello" -> C("goodbye"))
for ((a,b) <- list) {
b.f(a)
}
修改的
如果列表本身的类型超出您的控制范围,您仍然可以将其强制转换为&#34;更正&#34;类型。
case class C[T](x:T) {
def f(t:T) = println(t)
type ValueType = T
}
val list = List(1 -> C(2), "hello" -> C("goodbye"))
type CP[T] = (T, C[T])
for ((a,b) <- list.asInstanceOf[List[CP[T forSome { type T }]]]) {
b.f(a)
}
答案 2 :(得分:3)
好问题!这里有很多关于Scala的知识。
其他答案和评论已经解决了大部分问题,但我想补充几点。
你问为什么这个变体不起作用:
val (a2,b2) = (a,b).asInstanceOf[(T,C[T]) forSome {type T}]
b2.f(a2)
你不是唯一一个对此感到惊讶的人;见例如最近这个非常类似的问题报告:SI-9899。
正如我在那里写的那样:
我认为这是按照SLS 6.1的设计工作的:“以下的每个表达式通常应用了一个skolemization规则:如果表达式的类型是一个存在类型T,那么表达式的类型是我认为这是对T的重新认识。“
基本上,每次编写一个编译器确定具有存在类型的值级表达式时,都会实例化存在类型。 b2.f(a2)
有两个具有存在类型的子表达式,即b2
和a2
,因此存在主义获得两种不同的实例化。
至于模式匹配变体的工作原理,SLS 8(模式匹配)中没有明确的语言来涵盖存在类型的行为,但6.1不适用,因为模式在技术上不是表达式,它是一种模式。模式作为一个整体进行分析,内部的任何存在类型只能实例化(skolemized)一次。
作为附言,请注意,是的,当您在此区域玩游戏时,您收到的错误消息通常会令人困惑或误导,应该进行改进。请参阅示例https://github.com/scala/scala-dev/issues/205
答案 3 :(得分:2)
一个疯狂的猜测,但是你可能需要这样的东西:
case class C[+T](x:T) {
def f[A >: T](t: A) = println(t)
}
val list = List(1 -> C(2), "hello" -> C("goodbye"))
for ((a,b) <- list) {
b.f(a)
}
它会输入检查。
我不太确定&#34;运行时保证&#34;意思是这里,通常意味着你试图愚弄类型系统(例如asInstanceOf
),但是所有的赌注都关闭了,你不应该期望类型系统有任何帮助。
<强>更新强>
仅仅为了说明为什么类型转换是邪恶的:
case class C[T <: Int](x:T) {
def f(t: T) = println(t + 1)
}
val list = List("hello" -> C(2), 2 -> C(3))
for ((a, b: C[t]) <- list) {
b.f(a.asInstanceOf[t])
}
它在运行时编译并失败(毫不奇怪)。
<强> UPDATE2 强>
这是最后一个代码段生成代码的内容(使用C[t]
):
...
val a: Object = x1._1();
val b: Test$C = x1._2().$asInstanceOf[Test$C]();
if (b.ne(null))
{
<synthetic> val x2: Test$C = b;
matchEnd4({
x2.f(scala.Int.unbox(a));
scala.runtime.BoxedUnit.UNIT
})
}
...
类型t
只是消失了(因为它应该已经消失)而Scala正试图将a
转换为T
中C
的上限,即{{1} }}。如果没有上限,它将成为Int
(但是方法Any
几乎无用,除非你再次施放或使用f
之类的println
})。