在Scala中进行类型擦除匹配

时间:2017-08-22 03:47:41

标签: 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. 如何让编译器警告这种特殊情况?

4 个答案:

答案 0 :(得分:1)

您可以在响应中公开对外部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")
}

答案 1 :(得分:1)

由于内部特征 Response运行时中没有用于outer类型检查的实现Path dependent方法,这导致特质实际上是 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

答案 2 :(得分:1)

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的类型。

在您的情况下,您已定义了Requester的2个实例。 StringRequesterIntegerRequestertype的回复StringRequestRequester[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 ---")
  }

答案 3 :(得分:1)

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

我无法找到release notes for 2.12.0中指定的此更改,因此可能不是故意的。强烈建议用单元测试来覆盖它。