【问题标题】:How to pattern match on generic type in Scala?如何在 Scala 中对泛型类型进行模式匹配?
【发布时间】:2013-04-10 00:21:37
【问题描述】:

假设我们有一个泛型类Container

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

然后我们希望将ContainerDoubleContainerAny 进行模式匹配:

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

但是,编译器给出了两个警告,前两种情况各一个。例如,第一个警告说:“类型模式 Container[String] 中的非变量类型参数 String 未选中,因为它已被擦除消除”。由于擦除,在运行时无法区分不同种类的容器,并且会匹配第一个catch。因此,Container[Double] 类型的容器将与第一个 case 匹配,该 case 捕获 Container[String] 对象,因此将在 Double 上调用 toUpperCase 方法并抛出 java.lang.ClassCastException

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

【问题讨论】:

  • 我在那边添加了同一个问题的答案:link

标签: scala pattern-matching type-erasure


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

    也许这会有所帮助

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

    编辑:

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

    【讨论】:

    • 值得注意的是,在最近的 Scala 版本中,Manifest 已被弃用,取而代之的是 TypeTag
    【解决方案3】:

    可能的解决方法是使用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 =&gt; ...。 Scala 鼓励利用静态类型并避免在运行时检查类型。这与 Haskell/ML 世界的哲学是一致的。 case 子句不应匹配 types,而应匹配 constructors

    为解决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)
    

    现在将匹配 constructors,而不是 types

    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 课程,否则您无法访问它。

    【讨论】:

      【解决方案4】:

      注意:您还可以使用Miles SabinShapeless library (already mentioned by Miles in 2012 here) 替代。

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

      Typeable 是一个类型类,可以将值从 Any 类型转换为特定类型
      转换操作的结果是Option,其中Some 值将包含转换成功的值,None 值表示转换失败。

      TypeCase 桥接Typeable 和模式匹配。它本质上是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 强制转换基于列表的值。如果列表为空,那么自然是一个有效的字符串列表和一个有效的整数列表(或任何其他列表)

      【讨论】:

      • 链接的文章确实很好地处理了这个主题,并且还涵盖了其他替代方案(运行时反射等)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-07-06
      • 2016-09-04
      • 1970-01-01
      • 2021-10-25
      • 2019-08-04
      • 2013-12-21
      相关资源
      最近更新 更多