M. Karassev
M. Karassev

Reputation: 79

Unable to match a parameterized type with a concrete type after pattern-matching

Using scala 2.12.8 this would not compile without a cast:

trait Content
case object A extends Content
case class B(i: Int) extends Content

def asList[C <: Content](content: C): List[C] = content match {
  case A => List(A) // compiles
  case b: B => List(b) // does not compile
}
type mismatch;
 found   : b.type (with underlying type Playground.this.B)
 required: C

Here's a Scastie link to the problem: https://scastie.scala-lang.org/JIziYOYNTwKoZpdCIPCvdQ

Why is working for the case object and not for the case class? How can I make it work for the case class?

EDIT

The first answers made me realize I oversimplified my problem, here's an updated version :

sealed trait Content
case object A extends Content
final case class B(i: Int) extends Content

sealed trait Container[+C <: Content]
case class ContainerA(content: A.type) extends Container[A.type]
case class ContainerB(content: B) extends Container[B]

object Container {
  def apply[C <: Content](content: C): Container[C] = content match {
    case A => ContainerA(A) // compiles
    case b: B => ContainerB(b) // does not compile
  }
}

Scastie link: https://scastie.scala-lang.org/TDlJM5SYSwGl2gmQPvKEXQ

C cannot be a subtype of B since B is final.

Upvotes: 1

Views: 100

Answers (3)

Tim
Tim

Reputation: 27421

The solution is given in the comment by @lasf:

def asList[C <: Content](content: C): List[C] = content match {
  case A => List(A) // compiles
  case b: B => List(content) // compiles
}

The problem is that the return type is List[C] but the compiler cannot guarantee that the type of List(b) is List[C]. In particular, C could be a subtype of B in which case List(b) would List[B] which is not compatible with List[C].


The updated version can be solved using asInstanceOf, though it is not pretty.

def apply[C <: Content](content: C): Container[C] = content match {
  case A => ContainerA(A) // compiles
  case b: B => ContainerB(b).asInstanceOf[Container[C]]
}

Alternatively, you could take a different approach and use implicit conversion:

object Container {
  implicit def contain(content: A.type): Container[A.type] = ContainerA(content)
  implicit def contain(content: B): Container[B] = ContainerB(content)
}

val ca: Container[A.type] = A
val cb: Container[B] = B(0)

Or even multiple constructors:

object Container {
  def apply(content: A.type): Container[A.type] = ContainerA(content)
  def apply(content: B): Container[B] = ContainerB(content)
}

Here is an alternative design using a typeclass. This replaces the Content superclass with a Containable typeclass. The Container class can now contain anything as long as there is an instance of Containable for that class.

case object A
case class B(i: Int)

sealed trait Container[C]
case class ContainerA(content: A.type) extends Container[A.type]
case class ContainerB(content: B) extends Container[B]

trait Containable[T] {
  def apply(value: T): Container[T]
}
object Containable {
  implicit object AContainer extends Containable[A.type] {
    def apply(value: A.type) = ContainerA(value)
  }
  implicit object BContainer extends Containable[B] {
    def apply(value: B) = ContainerB(value)
  }
}

object Container {
  def apply[C](content: C)(implicit containable: Containable[C]): Container[C] =
    containable(content)
}

Upvotes: 3

Alexey Romanov
Alexey Romanov

Reputation: 170805

C cannot be a subtype of B since B is final.

Wrong!

Singleton types of B instances are subtypes of B:

val b = B(0)
val container: Container[b.type] = Container[b.type](b)

Since ContainerB doesn't extend Container[b.type], it can't be returned by the last line. And it can't be changed so that it does;

case class ContainerB(content: B) extends Container[content.type]

is not legal in Scala.

Null is also a subtype of B and you can create a similar example. And so are refinement types like B { type T = Int }.

Other subtypes which are probably irrelevant because they don't have instances: Nothing, compound types like B with Iterable[Int]...

Upvotes: 1

Chaitanya
Chaitanya

Reputation: 3638

The reason why you are getting an error is because of the return type of the method is not explicit. On replacing the return type from List[C] to List[Content] solves the problem.

def asList[C <: Content](content: C): List[Content] = content match {
  case A => List(A) // compiles
  case b: B => List(b) // compiles
}

Upvotes: 1

Related Questions