如何在 Scala 中对泛型类型进行模式匹配?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/16056645/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to pattern match on generic type in Scala?
提问by Calin-Andrei Burloiu
Let's suppose we have a generic class Container:
假设我们有一个泛型类Container:
case class Container[+A](value: A)
We then want to pattern match a Containerwith a Doubleand a Containerof Any:
然后我们想要模式匹配 aContainer与 aDouble和 a Containerof Any:
val double = Container(3.3)
var container: Container[Any] = double
To do this, we would normally write:
为此,我们通常会这样写:
container match {
case c: Container[String] => println(c.value.toUpperCase)
case c: Container[Double] => println(math.sqrt(c.value))
case _ => println("_")
}
However, the compiler gives two warnings, one for each of the first two cases. For example, the first warning says: "non-variable type argument String in type pattern Container[String] is unchecked since it is eliminated by erasure". Because of the erasure, it is impossible during runtime to distinguish between different kinds of containers and the first catch will be matched. As a consequence, container of type Container[Double]will be matched by the first case, which catches Container[String]objects, so toUpperCasemethod will be called on a Doubleand a java.lang.ClassCastExceptionwill be thrown.
但是,编译器给出了两个警告,前两种情况各有一个警告。例如,第一个警告说:“类型模式 Container[String] 中的非变量类型参数 String 未选中,因为它被擦除消除了”。由于擦除,在运行时无法区分不同类型的容器,并且会匹配第一个捕获。因此,类型Container[Double]为容器的容器将与捕获Container[String]对象的第一种情况匹配,因此toUpperCase将在 a 上调用方法Double并java.lang.ClassCastException抛出 a。
How to match a Containerparametrized by a particular type?
如何匹配Container特定类型的参数化?
回答by drexin
In general rarry's answer is correct, for your case however it can be simplified, because your container only contains a single value of a generic type, so you can match on that value's type directly:
一般来说,rarry 的答案是正确的,但对于您的情况,它可以简化,因为您的容器仅包含泛型类型的单个值,因此您可以直接匹配该值的类型:
container match {
case Container(x: String) => println("string")
case Container(x: Double) => println("double")
case _ => println("w00t")
}
回答by rarry
Maybe this will help
也许这会有所帮助
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:
编辑:
As Impredicative pointed out, Manifest is deprecated. Instead you could do the following:
正如 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")
}
回答by Calin-Andrei Burloiu
A possible workaround for this could be to use isInstanceOfand asInstanceOf.
一个可能的解决方法是使用isInstanceOf和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("_")
}
This works, but it doesn't look elegant at all. Professor Martin Odersky, the creator of Scala, says that isInstanceOfand asInstanceOfshould be avoided.
这有效,但它看起来一点也不优雅。马丁·奥德斯基教授斯卡拉的创造者说,isInstanceOf并asInstanceOf应避免。
As Rob Norris pointed me out, on the forum of the course "Functional programming in Scala" from Coursera, matching by type is a bad practice: case foo: Bar => .... Scala encourages to take advantage of static typing and avoid checking type during runtime. This is consistent with the philosophy of Haskell/ML world. Instead of matching types, caseclauses should match constructors.
正如 Rob Norris 指出的那样,在Coursera 的“ Scala 函数式编程”课程的论坛上,按类型匹配是一种不好的做法:case foo: Bar => .... Scala 鼓励利用静态类型并避免在运行时检查类型。这与 Haskell/ML 世界的哲学是一致的。相反匹配的类型,case条款应匹配构造。
To solve the Containermatching problem, a special container for each type can be defined:
为了解决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)
And now constructorswill be matched, not types:
现在将匹配构造函数,而不是类型:
container match {
case StringContainer(x) => println(x.toUpperCase)
case DoubleContainer(x) => println(math.sqrt(x))
case _ => println("_")
}
Apparently, we could be defined unapplymethods in two objects, StringContainerand DoubleContainerand use the same match as above, instead of extending the Containerclass:
显然,我们可以定义unapply两个对象的方法,StringContainer并DoubleContainer与使用相同的比赛如上,而不是扩展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)
}
But this does not work, again, because of JVM type erasure.
但这又不起作用,因为 JVM 类型擦除。
A reference to Rob Norris post, which lead me to this answer can be found here: https://class.coursera.org/progfun-002/forum/thread?thread_id=842#post-3567. Unfortunately, you can't access it unless you are enrolled in the Coursera course.
可以在此处找到对 Rob Norris 帖子的引用,该帖子将我引向此答案:https: //class.coursera.org/progfun-002/forum/thread?thread_id=842#post- 3567。不幸的是,除非您注册了 Coursera 课程,否则您无法访问它。
回答by VonC
Note: you also have an alternative with Miles Sabin's Shapeless library(already mentioned by Miles in 2012 here).
注意:您还可以使用Miles Sabin的Shapeless 库(Miles 在 2012 年在这里提到)。
You can see an example in "Ways to pattern match generic types in Scala" from Jaakko Pallari
您可以在Jaakko Pallari 的“在 Scala 中模式匹配泛型类型的方法”中看到一个示例
Typeableis a type class that provides the ability to cast values fromAnytype to a specific type.
The result of the casting operation is anOptionwhere theSomevalue will contain the successfully casted value, and theNonevalue represents a cast failure.
TypeCasebridgesTypeableand pattern matching. It's essentially an extractor forTypeableinstances
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.
While
Typeablemay look like it has what it takes to solve type erasure, it's still subject to the same behaviour as any other runtime code.
This can be seen in the last lines of the previous code examples where empty lists were recognized as string lists even when they were specified to be integer lists. This is becauseTypeablecasts are based on the values of the list. If the list is empty, then naturally that is a valid string list and a valid integer list (or any other list for that matter)
虽然
Typeable它看起来似乎具有解决类型擦除的能力,但它仍然与任何其他运行时代码具有相同的行为。
这可以在前面代码示例的最后几行中看到,其中空列表被识别为字符串列表,即使它们被指定为整数列表。这是因为Typeable强制转换基于列表的值。如果列表为空,那么自然是一个有效的字符串列表和一个有效的整数列表(或任何其他与此相关的列表)

