【问题标题】:Troubles with collections type in pattern matching in ScalaScala中模式匹配中集合类型的问题
【发布时间】:2019-08-06 18:44:07
【问题描述】:

我尝试在不使用 scala.reflect.ClassTag 的情况下在 Scala 中进行集合匹配

case class Foo(name: String)
case class Bar(id: Int)
case class Items(items: Vector[AnyRef])

val foo = Vector(Foo("a"), Foo("b"), Foo("c"))
val bar = Vector(Bar(1), Bar(2), Bar(3))

val fc = Items(foo)
val bc = Items(bar)

我们不能这样做:

fc match {
  case Items(x) if x.isInstanceOf[Vector[Foo]]
}

因为:

警告:scala.collection.immutable.Vector[Foo](Vector[Foo] 的底层)中的非变量类型参数 Foo 未检查,因为它已被擦除消除

还有这个:

fc match {
  case Items(x: Vector[Foo]) =>
}

但我们可以这样做:

fc match {
  case Items(x@(_: Foo) +: _) => ...
  case Items(x@(_: Bar) +: _) => ...
}

bc match {
  case Items(x@(_: Foo) +: _) => ...
  case Items(x@(_: Bar) +: _) => ...
}

如您所见,我们正在检查 - 是集合 Foo + 矢量还是 Bar + 矢量。

我们遇到了一些问题:

  1. 我们可以做 Vector(Foo("1"), Bar(2)),这将与 Foo 匹配。
  2. 我们仍然需要 "val result = x.asInstanceOf[Vector[Bar]]" 类转换来提取结果

还有什么更漂亮的方式吗? 像这样:

fc match {
  case Items(x: Vector[Foo]) => // result is type of Vector[Foo] already
}

【问题讨论】:

  • 只需使用ClassTag ...“Off topic”部分应该有一个“XY-problem”子部分。

标签: scala generics pattern-matching scala-collections


【解决方案1】:

你在这里所做的基本上只是有点不愉快,所以我不确定以一种美丽的方式做到这一点是一件好事,但对于它的价值,Shapeless's @987654322 @更好一点:

case class Foo(name: String)
case class Bar(id: Int)
case class Items(items: Vector[AnyRef])

val foo = Vector(Foo("a"), Foo("b"), Foo("c"))
val bar = Vector(Bar(1), Bar(2), Bar(3))

val fc = Items(foo)
val bc = Items(bar)

val FooVector = shapeless.TypeCase[Vector[Foo]]
val BarVector = shapeless.TypeCase[Vector[Bar]]

然后:

scala> fc match {
     |   case Items(FooVector(items)) => items
     |   case _ => Vector.empty
     | }
res0: Vector[Foo] = Vector(Foo(a), Foo(b), Foo(c))

scala> bc match {
     |   case Items(FooVector(items)) => items
     |   case _ => Vector.empty
     | }
res1: Vector[Foo] = Vector()

请注意,虽然ClassTag 实例也可以以这种方式使用,但它们不会做你想做的事:

scala> val FooVector = implicitly[scala.reflect.ClassTag[Vector[Foo]]]
FooVector: scala.reflect.ClassTag[Vector[Foo]] = scala.collection.immutable.Vector

scala> fc match {
     |   case Items(FooVector(items)) => items
     |   case _ => Vector.empty
     | }
res2: Vector[Foo] = Vector(Foo(a), Foo(b), Foo(c))

scala> bc match {
     |   case Items(FooVector(items)) => items
     |   case _ => Vector.empty
     | }
res3: Vector[Foo] = Vector(Bar(1), Bar(2), Bar(3))

...如果你尝试使用res3,它当然会抛出ClassCastExceptions。

不过,这确实不是一件好事——在运行时检查类型会破坏参数性,降低代码的健壮性等。类型擦除是一件好事,而 JVM 上类型擦除的唯一问题是还不够完整。

【讨论】:

  • 这是我的第一个想法。使用某种类型进行模式匹配。谢谢!
【解决方案2】:

如果您想要使用隐式转换简单的东西。那就试试吧!

implicit def VectorConversionI(items: Items): Vector[AnyRef] = items match { case x@Items(v) => v }

Example:

val fcVertor: Vector[AnyRef] = fc // Vector(Foo(a), Foo(b), Foo(c))

val bcVertor: Vector[AnyRef] = bc // Vector(Bar(1), Bar(2), Bar(3))

【讨论】:

  • 这怎么能远程回答这个问题?
猜你喜欢
  • 2017-08-18
  • 1970-01-01
  • 2018-01-03
  • 2020-08-09
  • 2011-08-11
  • 2018-11-26
  • 1970-01-01
  • 2013-03-17
相关资源
最近更新 更多