1
votes

I tried to do collection matching in Scala without using scala.reflect.ClassTag

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)

we can not do this:

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

because:

Warning: non-variable type argument Foo in type scala.collection.immutable.Vector[Foo] (the underlying of Vector[Foo]) is unchecked since it is eliminated by erasure

and this:

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

but we can do this:

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

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

As you can see, we are check - is collection Foo + vector or Bar + vector.

And here we are have some problems:

  1. We can do Vector(Foo("1"), Bar(2)), and this is will be match with Foo.
  2. We are still need "val result = x.asInstanceOf[Vector[Bar]]" class casting for result extraction

Is there are some more beautiful way? Like this:

fc match {
  case Items(x: Vector[Foo]) => // result is type of Vector[Foo] already
}
2
Just use ClassTag ... The "Off topic" section should really have an "XY-problem" subsection.Dima

2 Answers

4
votes

What you're doing here is fundamentally just kind of unpleasant, so I'm not sure making it possible to do it in a beautiful way is a good thing, but for what it's worth, Shapeless's TypeCase is a little nicer:

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]]

And then:

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()

Note that while ClassTag instances can also be used in this way, they don't do what you want:

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))

…which will of course throw ClassCastExceptions if you try to use res3.

This really isn't a nice thing to do, though—inspecting types at runtime undermines parametricity, makes your code less robust, etc. Type erasure is a good thing, and the only problem with type erasure on the JVM is that it's not more complete.

-2
votes

If you want something that is simple using implicit conversions. then try this!

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))