在 Scala 中解决类型擦除匹配问题



这是示例代码:

object GenericsTest extends App {
sealed trait CommonResponse
trait Requester[SomeType] {
trait Response extends CommonResponse {
def entity: SomeType
}
case class SomeResponse(entity: SomeType) extends Response
// More Responses here...
def getResponse(): Response
}
object StringRequester extends Requester[String] {
override def getResponse(): StringRequester.Response = SomeResponse("somestring")
}
object IntegerRequester extends Requester[Integer] {
override def getResponse(): IntegerRequester.Response = SomeResponse(42)
}
val response: CommonResponse = IntegerRequester.getResponse()
response match {
case r: StringRequester.Response => println(s"Got string response ${r.entity}") // hits here =(
case r: IntegerRequester.Response => println(s"Got integer response ${r.entity}")
case other => println(s"Got other $other")
}
}

它打印"得到字符串响应42"而不是"得到整数响应 42">

真正的代码是一种服务,它实现了不同类型的索引,如果数据已经被索引,将返回不同的响应,等等。

  1. 有什么解决方法吗?
  2. 如何使编译器警告这种特殊情况?

正如我在评论中所说,用abstract class Response替换trait Response可以解决 Scala 2.12 中的问题。这将导致捕获指针$outer并在模式匹配中对其进行检查(您可以使用编译器参数查看它-Xprint:jvm

我无法在 2.12.0 的发行说明中找到此更改,因此可能不是故意的。强烈建议通过单元测试来涵盖这一点。

您可以从响应中公开对外部Requester的引用:

trait Requester[SomeType] {
trait Response {
def requester: Requester.this.type = Requester.this
// ...
}
// ...
}

然后,您可以在response.requester上匹配:

response.requester match {
case StringRequester => println(s"Got string response ${response.entity}") // hits here =(
case IntegerRequester => println(s"Got integer response ${response.entity}")
case _ => println(s"Got other $response")
}

由于内部特征Response没有在运行时Path dependent类型检查的实现outer方法,这导致特征实际上是Java中的一个interface

因此,对于您的示例,您应该使用SomeResponse进行类型匹配,例如:

response match {
case r: StringRequester.SomeResponse => println(s"Got string response ${r.entity}") // hits here =(
case r: IntegerRequester.SomeResponse => println(s"Got integer response ${r.entity}")
case _ => println(s"Got other")
}

SomeResponse拥有在运行时键入检查outer方法。 请参阅:

public Test$Requester Test$Requester$SomeResponse$$$outer();
Code:
0: aload_0
1: getfield      #96                 // Field $outer:LTest$Requester;
4: areturn

Scala 在运行时擦除泛型类型。例如,List[String] 和 List[Integer] 的运行时类型是相同的。因此,您的代码不起作用。

例如

sealed trait Foo
case class Bar[T](value: T) extends Foo
val f1: Foo = Bar[String]("Apple")
val f2: Foo = Bar[Integer](12)
//Will print ---A even type of f2 is Integer. 
f2 match {
case x: Bar[String]=> println("--- A")
case x: Bar[Integer] => println("--- B")
case _ => println("---")
}

上面会打印---A,因为在scala中,泛型的类型在运行时被擦除,因此编译器不会知道f2的类型。

在您的情况下,您已经定义了 2 个Requester实例。StringRequesterIntegerRequester.StringRequest的响应typeRequester[String]#ResponseIntegerRequesterRequester[String]#Response。 在这里,Response路径相关类型,即响应类型因实例而异。 例如,StringRequester1.Response不等于StringRequester2.Response

但是,由于泛型,上述条件将失败。因为,由于泛型中的类型擦除,类型SomeType在运行时从Requester中删除。

i.e. Requester[String]#Response will be Requester[_]#Response
and Requester[Integer]#Response will be Requester[_]#Response
StringRequester.getResponse().isInstanceOf[IntegerRequester.Response] //will print true.
//because both type of StringRequester.getResponse() and IntegerRequest.Response is Requester[_]#Response.

因此,两种类型是相等的。因此,您的代码无法给出正确的结果。

response match {
case r: StringRequester.Response => println(s"Got string response ${r.entity}") // hits here =(
case r: IntegerRequester.Response => println(s"Got integer response ${r.entity}")
case other => println(s"Got other $other")
}

在上面的代码中,在这两种情况下,r类型在运行时都是Requester[_]#Response的,因此两者都会匹配,而 Scala 匹配第一个找到的情况,即StringRequester.Response.如果按如下所示交换位置,它将打印整数。

response match {
case r: IntegerRequester.Response => println(s"Got integer response ${r.entity}")
case r: StringRequester.Response => println(s"Got string response ${r.entity}") // hits here =(
case other => println(s"Got other $other")
}

解决方法如下:您可以使用反射类型检查,如下所示。

sealed trait Foo
case class Bar[T : TypeTag](value: T) extends Foo {
def typeCheck[U: TypeTag] = typeOf[T] =:= typeOf[U]
}
val f1:Foo = Bar[String]("apple")
val f2:Foo = Bar[Integer](12)
// Will print Integer type.
f2 match {
case x: Bar[String] if x.typeCheck[String] => println("Value is string type.")
case x: Bar[Integer] if x.typeCheck[Integer] => println("Value is Integer type.")
case _ => println("--- none ---")
}

最新更新