How can I return a subclass of a type from a method with a generic, bounded type?

78 Views Asked by At

Given the below example, how can I make the code compile without resorting to casting in myMethod?

object Test {
  sealed trait MyTrait
  case class A(v: Int) extends MyTrait
  case class B(v: Int) extends MyTrait

  def myMethod[T <: MyTrait](in0: T): T = {
    in0 match {
      case in: A => in.copy(v = 1)
      case in: B => in.copy(v = 2)
    }
  }

}

Thanks.

2

There are 2 best solutions below

3
Mateusz Kubuszok On BEST ANSWER

You can try to:

  • create some evidence which would transform your current type into T
  • initiate it with an identity
  • somehow make Scala
    • on one hand adjust input type to the same things as you in value
    • keep this evidence paired to whatever is matched

For instance you could implement it like this

import scala.annotation.nowarn

sealed trait MyTrait
case class A(v: Int) extends MyTrait
case class B(v: Int) extends MyTrait

@nowarn
def myMethod[T <: MyTrait](in0: T): T = {
  case class IntoT[A, B](value: A, upcast: A => B)
  IntoT(in0, identity[T]) match {
    case IntoT(in : A, upcastA: (A => T)) => upcastA(in.copy(v = 1))
    case IntoT(in : B, upcastB: (B => T)) => upcastB(in.copy(v = 2))
  }
}

It will make mismatching types error go away. Instead you will get warning that

match may not be exhaustive.

It would fail on pattern case: IntoT(_, _)

which in turn could be suppressed with @nowarn.

However, to be honest in such cases when I know that I know better than the compiler - because code is trivial and obvious at a glance, and I also cover it with tests - .asInstanceOf is not that bad. Sometimes you cannot avoid it so it's good to create some small, well tested utility which will help to deal with all cases where compiler was not so smart.

2
Eastsun On

How about this one:

object Test {
  sealed trait MyTrait[T]{
    def copy(v: Int): T
  }
  case class A(v: Int) extends MyTrait[A]
  case class B(v: Int) extends MyTrait[B]

  def myMethod[T <: MyTrait[T]](in0: T): T = {
    in0 match {
      case _: A => in0.copy(v = 1)
      case _: B => in0.copy(v = 2)
    }
  }

}

And there is another solution:

object Test {
  
  trait MyTrait
  case class A(v: Int) extends MyTrait
  case class B(v: Int) extends MyTrait

  import scala.language.reflectiveCalls
  def myMethod[T <: MyTrait](in: {def copy(v: Int): T}): T = in match {
      case _: A => in.copy(v = 1)
      case _: B => in.copy(v = 2)
  }
  val a: A = myMethod(A(1))
    val b: B = myMethod(B(2))
}