首页 文章

如何在Scala中对泛型类型进行模式匹配?

提问于
浏览
26

假设我们有一个泛型类 Container

case class Container[+A](value: A)

然后,我们想要将 ContainerDoubleContainer Any 进行模式匹配:

val double = Container(3.3)  
var container: Container[Any] = double

为此,我们通常会写:

container match {  
  case c: Container[String] => println(c.value.toUpperCase)
  case c: Container[Double] => println(math.sqrt(c.value))  
  case _ => println("_")  
}

但是,编译器会给出两个警告,前两个案例各一个 . 例如,第一个警告说:"non-variable type argument String in type pattern Container[String] is unchecked since it is eliminated by erasure" . 由于擦除,在运行期间不可能区分不同类型的容器并且第一个捕获物将匹配 . 因此, Container[Double] 类型的容器将匹配第一个案例,该案例捕获 Container[String] 个对象,因此将在 Double 上调用 toUpperCase 方法,并抛出 java.lang.ClassCastException .

如何匹配特定类型的 Container 参数化?

4 回答

  • 4

    一般来说rarry的答案是正确的,但是对于你的情况它可以简化,因为你的容器只包含泛型类型的单个值,所以你可以直接匹配该值的类型:

    container match {
      case Container(x: String) => println("string")
      case Container(x: Double) => println("double")
      case _ => println("w00t")
    }
    
  • 30

    也许这会有所帮助

    def matchContainer[A: Manifest](c: Container[A]) = c match {
          case c: Container[String] if manifest <:< manifest[String] => println(c.value.toUpperCase)
          case c: Container[Double] if manifest <:< manifest[Double] => println(math.sqrt(c.value))
          case c: Container[_] => println("other")
        }
    

    Edit:

    正如Impredicative所指出的,Manifest已被弃用 . 相反,你可以做以下事情:

    import reflect.runtime.universe._
    def matchContainer[A: TypeTag](c: Container[A]) = c match {
          case c: Container[String] if typeOf[A] <:< typeOf[String] => println("string: " + c.value.toUpperCase)
          case c: Container[Double] if typeOf[A] <:< typeOf[Double] => println("double" + math.sqrt(c.value))
          case c: Container[_] => println("other")
        }
    
  • 26

    可能的解决方法是使用 isInstanceOfasInstanceOf .

    container match {  
      case Container(x) if x.isInstanceOf[String] =>  
        println(x.asInstanceOf[String].toUpperCase)  
      case Container(x) if x.isInstanceOf[Double] =>  
        println(math.sqrt(x.asInstanceOf[Double]))  
      case _ => println("_")  
    }
    

    这样可行,但它看起来并不优雅 . Scala的创建者Martin Odersky教授说应该避免 isInstanceOfasInstanceOf .

    正如Rob Norris所指出的那样,在Coursera的“Scala中的函数式编程”课程的论坛上,按类型匹配是一种不好的做法: case foo: Bar => ... . Scala鼓励利用静态类型并避免在运行时检查类型 . 这符合Haskell / ML世界的哲学 . case 子句应匹配构造函数,而不是匹配类型 .

    要解决 Container 匹配问题,可以为每种类型定义一个特殊容器:

    class Container[+A](val value: A)
    
    case class StringContainer(override val value: String)
      extends Container(value)
    
    case class DoubleContainer(override val value: Double)
      extends Container(value)
    

    现在构造函数将匹配,而不是类型:

    container match {
      case StringContainer(x) => println(x.toUpperCase)
      case DoubleContainer(x) => println(math.sqrt(x))
      case _ => println("_")
    }
    

    显然,我们可以在两个对象 StringContainerDoubleContainer 中定义 unapply 方法,并使用与上面相同的匹配,而不是扩展 Container 类:

    case class Container[+A](val value: A)
    
    object StringContainer {
      def unapply(c: Container[String]): Option[String] = Some(c.value)
    }
    
    
    object DoubleContainer {
      def unapply(c: Container[Double]): Option[Double] = Some(c.value)
    }
    

    但是,由于JVM类型擦除,这也不起作用 .

    可以在此处找到Rob Norris帖子的引用,该帖子引导我找到这个答案:https://class.coursera.org/progfun-002/forum/thread?thread_id=842#post-3567 . 不幸的是,除非您已加入Coursera课程,否则无法访问它 .

  • 12

    注意:您还可以选择Miles SabinShapeless libraryalready mentioned by Miles in 2012 here) .

    你可以在Jaakko Pallari的“Ways to pattern match generic types in Scala”中看到一个例子

    Typeable是一个类型类,它提供将值从Any类型转换为特定类型的功能 . 转换操作的结果是一个Option,其中Some值将包含成功的转换值,None值表示转换失败 . TypeCase桥接可键入和模式匹配 . 它本质上是Typeable实例的提取器

    import shapeless._
    
    def extractCollection[T: Typeable](a: Any): Option[Iterable[T]] = {
      val list = TypeCase[List[T]]
      val set  = TypeCase[Set[T]]
      a match {
        case list(l) => Some(l)
        case set(s)  => Some(s)
        case _       => None
      }
    }
    
    val l1: Any = List(1, 2, 3)
    val l2: Any = List[Int]()
    val s:  Any = Set(1, 2, 3)
    
    extractCollection[Int](l1)    // Some(List(1, 2, 3))
    extractCollection[Int](s)     // Some(Set(1, 2, 3))
    extractCollection[String](l1) // None
    extractCollection[String](s)  // None
    extractCollection[String](l2) // Some(List()) // Shouldn't this be None? We'll get back to this.
    

    虽然Typeable可能看起来具有解决类型擦除所需的功能,但它仍然与任何其他运行时代码具有相同的行为 . 这可以在前面的代码示例的最后几行中看到,其中空列表被识别为字符串列表,即使它们被指定为整数列表 . 这是因为Typeable强制转换基于列表的值 . 如果列表为空,那么自然这是一个有效的字符串列表和一个有效的整数列表(或任何其他列表)

相关问题