Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala pattern-match on generics

I have a list of "string"s (a wrapper around class String, named Str), some of them with mixed traits. At some point in time I need to distinguish the mixin traits to provide additional functionalities.

My code can be resumed to this and it works fine:

case class Str(s: String)
trait A
trait B

object GenericsPatternMatch extends {
  def main(args: Array[String]): Unit = {

    val listOfStr: Seq[Str] =
      Seq(
        Str("String"),
        new Str("String A") with A, // Some trait mixins
        new Str("String B") with B
      )

    println("A: " + selectStrA(listOfStr))
    println("B: " + selectStrB(listOfStr))
  }

  val selectStrA: Seq[Str] => Seq[Str with A] = (strList: Seq[Str]) => strList.collect { case s: A => s }
  val selectStrB: Seq[Str] => Seq[Str with B] = (strList: Seq[Str]) => strList.collect { case s: B => s }
}

In order to keep the code according to the DRY principles, I would like to generify the selectStr functions. My first attempt was:

def selectStrType[T](strList: Seq[Str]): Seq[Str with T] =
    strList.collect { case f: Str with T => f }

However due to the JVM runtime type erasure feature (limitation?), the compiler gives a warning and it does not work, most likely because it will match everything with Object:

Warning:(31, 31) abstract type pattern T is unchecked since it is eliminated by erasure
    strList.collect { case f: Str with T => f }

After a few hours of searching and learning, I came up with:

def selectStrType[T: ClassTag](strList: Seq[Str]): Seq[Str with T] =
    strList.collect {
      case f: Str if classTag[T].runtimeClass.isInstance(f) => f.asInstanceOf[Str with T]
    }

With this method I'm now able to select specific traits like this:

val selectStrA: Seq[Str] => Seq[Str with A] = (strList: Seq[Str]) => selectStrType[A](strList: Seq[Str])
val selectStrB: Seq[Str] => Seq[Str with B] = (strList: Seq[Str]) => selectStrType[B](strList: Seq[Str])

I believe that there might be a way to improve selectStrType function, namely:

  1. Simplifying the if condition
  2. Removing the explicit cast ".asInstanceOf[Str with T]", but still returning a Seq[Str with T]

Can you help me?

like image 627
pedro bento Avatar asked Oct 28 '22 17:10

pedro bento


1 Answers

You can define your method as follows and it will work.

def selectStrType[T: ClassTag](strList: Seq[Str]): Seq[Str with T] =
  strList.collect { case f: T => f }

Because of the ClassTag context bound a type match on just T will work (ideally Str with T should also work, but this appears to be a limitation). Now the compiler knows that f has type Str and also type T, or in other words Str with T, so this compiles. And it will do the right thing:

scala> selectStrType[A](listOfStr)
res3: Seq[Str with A] = List(Str(String A))

scala> selectStrType[B](listOfStr)
res4: Seq[Str with B] = List(Str(String B))

EDIT: correction, it appears that this will work as of Scala 2.13. In 2.12 you need to help the compiler a little:

def selectStrType[T: ClassTag](strList: Seq[Str]): Seq[Str with T] =
  strList.collect { case f: T => f: Str with T }
like image 168
Jasper-M Avatar answered Nov 15 '22 07:11

Jasper-M