Scala:没有明确知道类型参数

时间:2016-08-20 22:43:55

标签: scala

考虑以下示例:

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]添加TError:(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来说它也太复杂了。)

4 个答案:

答案 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)有两个具有存在类型的子表达式,即b2a2,因此存在主义获得两种不同的实例化。

至于模式匹配变体的工作原理,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转换为TC的上限,即{{1} }}。如果没有上限,它将成为Int(但是方法Any几乎无用,除非你再次施放或使用f之类的println })。