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

Cal*_*oiu 26 scala pattern-matching type-erasure

我们假设我们有一个泛型类Container:

case class Container[+A](value: A)
Run Code Online (Sandbox Code Playgroud)

然后,我们希望模式匹配一个ContainerDoubleContainerAny:

val double = Container(3.3)  
var container: Container[Any] = double
Run Code Online (Sandbox Code Playgroud)

为此,我们通常会写:

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

但是,编译器会给出两个警告,前两个案例各一个.例如,第一个警告说:"类型模式容器[String]中的非变量类型参数字符串未被选中,因为它被擦除".由于擦除,在运行期间不可能区分不同类型的容器并且第一个捕获物将匹配.因此,类型的容器Container[Double]将与捕获Container[String]对象的第一个案例匹配,因此toUpperCase将在a上调用方法,Double并且java.lang.ClassCastException将抛出a.

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

dre*_*xin 30

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

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


rar*_*rry 29

也许这会有所帮助

 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")
    }
Run Code Online (Sandbox Code Playgroud)

编辑:

正如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")
    }
Run Code Online (Sandbox Code Playgroud)

  • 值得注意的是,在最新版本的Scala中,"Manifest"被弃用,而不赞成"TypeTag". (3认同)

Cal*_*oiu 13

可能的解决方法是使用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("_")  
}
Run Code Online (Sandbox Code Playgroud)

这样可行,但它看起来并不优雅.马丁·奥德斯基教授斯卡拉的创造者说,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)
Run Code Online (Sandbox Code Playgroud)

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

container match {
  case StringContainer(x) => println(x.toUpperCase)
  case DoubleContainer(x) => println(math.sqrt(x))
  case _ => println("_")
}
Run Code Online (Sandbox Code Playgroud)

显然,我们可以定义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)
}
Run Code Online (Sandbox Code Playgroud)

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

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


Von*_*onC 5

注意:您还可以使用Miles SabinShapeless库在Miles在2012年这里已经提到过)中找到替代方法。

您可以Jaakko Pallari的Scala中模式匹配通用类型的方式 ”中看到一个示例

Typeable是一个类型类,提供将Any类型的值转换为特定类型的功能
铸造操作的结果是OptionSome值将包含成功铸造价值,和None值表示铸造失败。

TypeCaseTypeable和模式匹配。它本质上是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.
Run Code Online (Sandbox Code Playgroud)

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