在Scala中处理类型擦除匹配

在Scala中处理类型擦除匹配,scala,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 //

这是示例代码:

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")
  }
}
它打印“Get string response 42”而不是“Get integer response 42”

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

  • 有解决办法吗
  • 如何使编译器对这种特殊情况发出警告

  • 您可以在响应中公开对外部
    请求者的引用:

    trait Requester[SomeType] {
      trait Response {
        def requester: Requester.this.type = Requester.this
        // ...
      }
      // ...
    }
    
    然后,您可以在
    响应上进行匹配。请求者

    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")
    }
    

    您可以在响应中公开对外部
    请求者的引用:

    trait Requester[SomeType] {
      trait Response {
        def requester: Requester.this.type = Requester.this
        // ...
      }
      // ...
    }
    
    然后,您可以在
    响应上进行匹配。请求者

    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")
    }
    

    由于内部trait
    响应
    运行时中没有用于
    路径相关
    类型检查的实现
    外部
    方法,这导致trait实际上是Java中的一个
    接口

    因此,对于您的示例,您应该使用
    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
    

    由于内部trait
    响应
    运行时中没有用于
    路径相关
    类型检查的实现
    外部
    方法,这导致trait实际上是Java中的一个
    接口

    因此,对于您的示例,您应该使用
    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
    的类型

    在本例中,您定义了两个
    请求者的实例。
    
    StringRequester
    IntegerRequester
    StringRequest
    响应类型是
    Requester[String]#response
    IntegerRequester
    Requester[String]#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 ---")
      }
    

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

    在本例中,您定义了两个
    请求者的实例。
    
    StringRequester
    IntegerRequester
    StringRequest
    响应类型是
    Requester[String]#response
    IntegerRequester
    Requester[String]#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 ---")
      }
    

    正如我在评论中所说,将
    特质响应
    替换为
    抽象类响应
    修复了Scala 2.12中的问题。这将导致捕获
    $outer
    指针并在模式匹配中检查它(您可以使用
    -Xprint:jvm
    编译器参数查看它)


    我找不到中指定的此更改,因此可能不是有意的。强烈建议使用单元测试来覆盖此更改。

    如我在评论中所述,将
    特征响应
    替换为
    抽象类响应
    修复了Scala 2.12中的问题。这将导致捕获
    $outer
    指针及其being检查模式匹配(您可以通过使用
    -Xprint:jvm
    编译器参数来查看它)


    我找不到中指定的此更改,因此可能不是有意的。强烈建议使用单元测试来覆盖此更改。

    -未选中以警告外部指针。如果您使用的是scala>=2.12.0,则仅替换
    特征响应