0

I have a class which contains a sequence of a generic type like:

sealed trait Interface {}

case class Imp1() extends Interface {}
case class Imp2() extends Interface {}

case class Wrapper[+I <: Interface](interface: I) {}

case class WrapperList(list: Seq[Wrapper[Interface]]) {
    ...
}

In the WrapperList I want to be able to iterate through the Sequence of wrappers and pattern match on each ones type, eg.

def getImp1s(remaining: Seq[Wrapper[Interface]] = list): Seq[Wrapper[Imp1]] = {
  if (remaining.length == 0) Seq()
  else remaining.head match {
    case wrapper: Wrapper[Imp1] => get(remaining.tail) :+ wrapper
    case _                      => get(remaining.tail)
  }
}

As you can probably guess I'm running into

non-variable type argument Playground.Imp1 in type pattern Playground.Wrapper[Playground.Imp1] is unchecked since it is eliminated by erasure

To overcome this I was under the impression I could use TypeTags or ClassTags to preserve the type, something like:

case class Wrapper[+I <: Interface](interface: I)(implicit tag: TypeTag[I]) {}

However this doesn't seem to work, I still get the same warning. Could someone explain how I can match using the TypeTag? I'd rather avoid creating concrete versions of my generic class which extend a generic abstract class, but do understand that this is maybe the easiest solution.

Thanks for your help :)

Suma
  • 33,181
  • 16
  • 123
  • 191
mufasa
  • 15
  • 1
  • 3
  • 2
    Probably [this](https://stackoverflow.com/a/21640639/9128490) should help – Duelist May 16 '20 at 13:28
  • Due type erasure this kind of designs will be painful to work with. Maybe, it would be better if you can explain what are your constraints and what do you need to do? – Luis Miguel Mejía Suárez May 16 '20 at 14:43
  • 1
    @LuisMiguelMejíaSuárez Yeah, I was kind of hopping that TypeTags would be a magic bullet that would just allow me to match on generics like they were concrete with no extra effort. I'm just learning Scala and was trying to do something in a weird way to help me understand how things work. Thanks – mufasa May 16 '20 at 15:00

1 Answers1

2

Try

import shapeless.TypeCase

val `Wrapper[Imp1]` = TypeCase[Wrapper[Imp1]]

def getImp1s(remaining: Seq[Wrapper[Interface]]): Seq[Wrapper[Imp1]] = {
  if (remaining.isEmpty) Seq()
  else remaining.head match {
    case `Wrapper[Imp1]`(wrapper) => getImp1s(remaining.tail) :+ wrapper
    case _                        => getImp1s(remaining.tail)
  }
}

getImp1s(Seq(Wrapper(Imp1()), Wrapper(Imp2()), Wrapper(new Interface {}))) 
// List(Wrapper(Imp1()))
getImp1s(Seq(Wrapper(Imp2()), Wrapper(Imp1()), Wrapper(new Interface {}))) 
// List(Wrapper(Imp1()))

The same can be achieved without Shapeless with custom extractor

object `Wrapper[Imp1]` {
  def unapply(arg: Any): Option[Wrapper[Imp1]] = arg match {
    case Wrapper(Imp1()) => Some(Wrapper(Imp1()))
    case _               => None
  }
}

or directly

def getImp1s(remaining: Seq[Wrapper[Interface]]): Seq[Wrapper[Imp1]] = {
  if (remaining.isEmpty) Seq()
  else remaining.head match {
    case Wrapper(Imp1()) => getImp1s(remaining.tail) :+ Wrapper(Imp1())
    case _               => getImp1s(remaining.tail)
  }
}

or

def getImp1s(remaining: Seq[Wrapper[Interface]]): Seq[Wrapper[Imp1]] = {
  if (remaining.isEmpty) Seq()
  else remaining.head match {
    case Wrapper(_: Imp1) => getImp1s(remaining.tail) :+ Wrapper(Imp1())
    case _                => getImp1s(remaining.tail)
  }
}
ljleb
  • 182
  • 1
  • 14
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • Thanks for the really comprehensive response it has cleared up how type tags work really well :) – mufasa May 16 '20 at 15:01