30

假设我们有一个泛型类Container

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

然后,我们希望将 aContainer与 aDouble和 aContainer进行模式匹配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("_")  
}

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

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

4

4 回答 4

32

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

container match {
  case Container(x: String) => println("string")
  case Container(x: Double) => println("double")
  case _ => println("w00t")
}
于 2013-04-17T10:04:47.980 回答
31

也许这会有所帮助

 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 所指出的,清单已被弃用。相反,您可以执行以下操作:

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")
    }
于 2013-04-17T09:55:10.353 回答
13

一个可能的解决方法是使用isInstanceOfand asInstanceOf

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

这行得通,但它看起来一点也不优雅。isInstanceOfScala 的创建者Martin Odersky 教授说asInstanceOf应该避免这种情况。

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

显然,我们可以unapply在两个对象中定义方法,StringContainerDoubleContainer使用与上面相同的匹配,而不是扩展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 课程,否则您无法访问它。

于 2013-04-17T09:39:58.610 回答
5

注意:您还可以使用Miles SabinShapeless 库Miles 在 2012 年在这里提到)。

您可以在Jaakko Pallari的“在 Scala 中模式匹配泛型类型的方法”中看到一个示例

Typeable是一个类型类,它提供将值从Any类型转换为特定类型的能力。转换操作的
结果是一个值将包含成功转换的值,该值表示转换失败。OptionSomeNone

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强制转换基于列表的值。如果列表为空,那么自然是一个有效的字符串列表和一个有效的整数列表(或任何其他列表)

于 2016-06-14T16:32:47.770 回答