Search code examples
scalawildcardcase-classexistential-type

Issue with using wildcard parameter twice in a case class


As peers the example below, I am trying to make a case class that can hold items of type SomeResult[T] without having to know what T is. This works fine in the case of Rawr, which can hold a Set of SomeResult[_], however when I add a second field to try and work with on the same principle (i.e. a single element who's content we don't care about, and a set of elements), I get the following error

[error] /Users/matthewdedetrich/temp/src/main/scala/Main.scala:15: type arguments [_$2] do not conform to class SomeResult's type parameter bounds [A <: T]
[error] case class Bleh(oneThing:SomeResult[_],moreThings:Set[SomeResult[_]]) // This doesn't

Here is the sample code

trait T {

}

case class First(int:Int) extends T
case class Second(int:Int) extends T


case class SomeResult[A <: T](name:String, t:A)

case class Rawr(multipleThings:Set[SomeResult[_]]) // This works

case class Bleh(oneThing:SomeResult[_],moreThings:Set[SomeResult[_]]) // This doesn't

There is a suggestion to use a [+A <: T] as a type bound instead of a wildcard, however the following code doesn't work when doing this

val t = Set(First(3),Second(5))

def someFunc[A <: T](thing:A) = {
    thing match {
      case First(_) => SomeResult("a",First(10))
      case Second(_) => SomeResult("b",Second(15))
      case _ => throw new IllegalArgumentException("rawr")
    }
  }

  val z = t.map{
    case x:First => someFunc(First(5))
    case y:Second => someFunc(Second(5))
    case _ => throw new IllegalArgumentException("rawr")
  }

  val z2 = Rawr(z)

Which then provides the error

[error]  found   : scala.collection.immutable.Set[SomeResult[Product with Serializable with T]]
[error]  required: Set[SomeResult[T]]
[error] Note: SomeResult[Product with Serializable with T] <: SomeResult[T], but trait Set is invariant in type A.
[error] You may wish to investigate a wildcard type such as `_ <: SomeResult[T]`. (SLS 3.2.10)

Which is why I used wildcard types in the first place. Funnily enough, if you try to provide a return type to sumFunc, you get the exact same problem (where the scala compiler error suggests that you should use Wildcard types)

EDIT 2: I have actually managed to get the code to compile by doing this

  def someFunc[A <: T](thing:A):SomeResult[A] = {
    thing match {
      case First(_) => SomeResult("a",First(10)).asInstanceOf[SomeResult[A]]
      case Second(_) => SomeResult("b",Second(15)).asInstanceOf[SomeResult[A]]
      case _ => throw new IllegalArgumentException("rawr")
    }
  }

  def z[A <: T]:Set[SomeResult[A]] = t.map{
    case x:First => someFunc(First(5)).asInstanceOf[SomeResult[A]]
    case y:Second => someFunc(Second(5)).asInstanceOf[SomeResult[A]]
    case _ => throw new IllegalArgumentException("rawr")
  }

Im not sure if its "idiomatic" or "right", but its the only way to get the Serializable with Product out of the type signature. I have no idea why Scala infers this when the result type is clearly a subtype of T


Solution

  • Why don't just use T instead of _?:

    case class Bleh(oneThing:SomeResult[T],moreThings:Set[SomeResult[T]]) // Compiles
    

    Then e.g. Bleh(SomeResult("", First(0)), Set()) works fine.

    Naming a trait as T is a bit unfortunate, it's easily confused with type parameter.

    Answer to EDIT2:
    Slightly edited and working:

    val t: Set[T] = Set(First(3), Second(5))
    
    def someFunc[A <: T](thing: A): SomeResult[T] = {
      thing match {
        case First(_) => SomeResult("a", First(10))
        case Second(_) => SomeResult("b", Second(15))
        case _ => throw new IllegalArgumentException("rawr")
      }
    }
    
    def z[A <: T]: Set[SomeResult[T]] = t.map {
      case x: First => someFunc(First(5))
      case y: Second => someFunc(Second(5))
      case _ => throw new IllegalArgumentException("rawr")
    }
    

    You were forcing a type A to Set, but Set is invariant (compiler was unable to make a Set[T] when got t of type First/Second [old method signatures was with A]).