Home > Mobile >  Scala error when providing subclass instance in place of superclass?
Scala error when providing subclass instance in place of superclass?

Time:04-02

I am just trying things out in scala and I wrote this code

object Main:
  def main(args: Array[String]): Unit =
    val dInt: Data[Int] = IntData(1)
    val dString: Data[String] = StringData("hello")
    val Data(deconstructedInt) = dInt // unapply
    val Data(deconstructedString) = dString // unapply
    println(deconstructedInt)
    println(deconstructedString)


sealed trait Data[ T]:
  def get: T

case class IntData(get: Int) extends Data[Int]

case class StringData(get: String) extends Data[String]

object Data:
  def apply[T](input: T): Data[T] = input match {
    case i: Int => IntData(i) //compile error here
    case s: String => StringData(s) //compile error here
  }

  def unapply[T](d: Data[T]): Option[String] = d match {
    case IntData(get) => Some(s"int data => get = $get")
    case StringData(get) => Some(s"string data => get = $get")
  }

I get at the location commented in the code this error

Found:    IntData
Required: Data[T]
    case i: Int => IntData(i)

why I am getting this error, isn't IntData (or StringData) a subclass of Data ?

CodePudding user response:

IntData is a subtype of Data[Int]. So if T is not Int, IntData is not a subtype of Data[T]. Now, you might say, if input matches the first case, then clearly T is Int. But the compiler is not smart to realise this!

You could try using Scala 3's new match types:

type DataOf[T] = T match {
  case Int => IntData
  case String => StringData
}

def apply[T](input: T): DataOf[T] = input match {
  case i: Int => IntData(i)
  case s: String => StringData(s)
}

Another alternative is union types:

def apply(input: Int | String): Data[Int | String] = input match {
  case i: Int => IntData(i)
  case s: String => StringData(s)
}

However, you will lose type information in doing this. Using the match types solution, apply(42) is inferred to have type IntData. Using the union types solution, it is inferred to have type Data[Int | String].

CodePudding user response:

This way compiler connects the dots between Ts and it works:

  object Main:
    def main(args: Array[String]): Unit =
      val dInt: Data[Int] = IntData(1)
      val dString: Data[String] = StringData("hello")
      val Data(deconstructedInt) = dInt // unapply
      val Data(deconstructedString) = dString // unapply
      println(deconstructedInt)
      println(deconstructedString)

  sealed trait Data[ T]:
    def get: T

  case class IntData[T <: Int](get: T) extends Data[T]

  case class StringData[T <: String](get: T) extends Data[T]

  object Data:
    def apply[T](input: T): Data[T] = input match {
      case i: Int    => IntData(i)
      case s: String => StringData(s)
    }

    def unapply[T](d: Data[T]): Option[String] = d match {
      case IntData(get)    => Some(s"int data => get = $get")
      case StringData(get) => Some(s"string data => get = $get")
    }
  • Related