如何模式匹配在Scalagenericstypes?
假设我们有一个generics类Container
:
case class Container[+A](value: A)
然后,我们想模式匹配一个Container
与一个Double
和任何Container
:
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("_") }
然而,编译器给出了两个警告,其中前两个是每个警告。 例如,第一个警告说:“types模式Container [String]中的非variablestypes参数String未被选中,因为它被擦除”。 由于擦除,在运行时不可能区分不同types的容器,并且第一次捕获将匹配。 因此, Container[Double]
types的Container[Double]
将被第一个与Container[String]
对象相匹配的case匹配,所以toUpperCase
方法将在Double
上被调用,并抛出java.lang.ClassCastException
。
如何匹配由特定types参数化的Container
?
一般来说,rarry的答案是正确的,但是你的情况可以简化,因为你的容器只包含一个genericstypes的值,所以你可以直接匹配该值的types:
container match { case Container(x: String) => println("string") case Container(x: Double) => println("double") case _ => println("w00t") }
也许这会有所帮助
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") }
编辑:
正如预测指出的那样,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") }
对此可能的解决方法可能是使用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("_") }
这工作,但它看起来并不高雅。 Scala的创始人Martin Odersky教授说,这是asInstanceOf
应该避免的。
正如Rob Norris指出的那样,在Coursera的“ Scala函数式编程 ”课程论坛上,按types匹配是一个不好的做法: case foo: Bar => ...
Scala鼓励利用静态types,避免在运行时检查types。 这与Haskell / ML世界的哲学是一致的。 case
子句应该匹配构造函数 ,而不是匹配types 。
为了解决Container
匹配问题,可以定义每种types的特殊容器:
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)
而现在构造函数将被匹配,而不是types :
container match { case StringContainer(x) => println(x.toUpperCase) case DoubleContainer(x) => println(math.sqrt(x)) case _ => println("_") }
显然,我们可以在两个对象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) }
但是由于JVMtypes的擦除,这又不起作用了。
罗布·诺里斯(Rob Norris)的文章引用了我的这个答案,可以在这里find: https : //class.coursera.org/progfun-002/forum/thread?thread_id= 842#post- 3567 。 不幸的是,除非你参加Coursera课程,否则你不能访问它。
注意:您也可以selectMiles Sabin的Shapeless图书馆 ( 这里已经在2012年提及Miles了 )。
您可以在 Jaakko Pallari的 “ 在Scala中匹配genericstypes的方法 ”中看到一个例子
Typeable
是一个types类,提供了将Any
types的值转换为特定types的能力 。
铸造操作的结果是一个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
可能看起来像它具有解决types擦除所需的东西,但它仍然受到与其他任何运行时代码相同的行为的影响。
这可以在前面的代码示例的最后几行中看到,空列表被识别为string列表,即使它们被指定为整数列表。 这是因为可Typeable
转换基于列表的值。 如果列表是空的,那么自然是一个有效的string列表和一个有效的整数列表(或任何其他列表)