5

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?

4
  • I wonder what your use case for this is? Is there a reason why you would loose the information of what Str is tagged with in the first place? Can that be prevented? Using reflection to recover that information is not a very performant pattern. What are you trying to accomplish with the type tagging? Commented Mar 14, 2019 at 13:28
  • Yeah, I was trying to remove the reflection, however I was unable to that on my own. Commented Mar 14, 2019 at 14:43
  • As for use case, I'm using it currently for a database schema and some columns have special features like: need additional validations or trigger some behavior over those columns. But I want the flexibility of adding a specific feature to a column, but not the other or mix features. For instance: Column("column_name") with Required Commented Mar 14, 2019 at 14:49
  • Are your traits ever going to be valid mixed in to anything that is not a Str? Commented Mar 14, 2019 at 22:15

1 Answer 1

2

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 }
Sign up to request clarification or add additional context in comments.

4 Comments

You should use Manifest instead of ClassTag. Its preferred and I believe ClassTag will be deprecated at some point. The code compiles exactly with that substitution.
@NigelBenns You definitely should not. It's the other way around.
That is perfect. I can also confirm that your 2.12 code version works in Scala 2.11.X
While you can make some things like this work using ClassTag, you should probably consider alternate designs. Requiring a ClassTag means you can only call selectStrType in contexts where T is concrete, or where you pass the ClassTag requirement upstream.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.