Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Incompatible pattern type when pattern matching a Class's generic parameter

Tags:

generics

scala

Why does pattern matching on a Class's generic constructor parameter fail, while that on a method's generic parameter succeed?

I have a type class Ev:

sealed trait Ev[T]
case object EvInt extends Ev[Int]
case object EvStr extends Ev[String]

and a generic class like so:

class C[T](ev: Ev[T]) {
  val id: Int = ev match {
    case EvInt => 0
    case EvStr => 1
  }
}

The above pattern match on ev fails with:

error: pattern type is incompatible with expected type;
 found   : EvInt.type
 required: Ev[T]
    case EvInt => 0

whereas this works:

object C {
  def id[T](ev: Ev[T]): Int = {
    ev match {
      case EvInt => 0
      case EvStr => 1
    }
  }
}

For my purpose, I could work around this in various ways, e.g., by moving id into the Ev trait:

sealed trait Ev[T] {
  def id: Int
}

class C[T](ev: Ev[T]) { val id: Int = ev.id }

but I am curious why it fails.

like image 584
avijitk Avatar asked Nov 16 '25 07:11

avijitk


1 Answers

That makes sense to think that it should've worked in the first place. And as some people commented, scala 2 had some flaws in ADTs and type inference/checking. And you already know the workarounds to this problem. So I'm just going to write my "experiments"/assumptions here.

The compiler says it requires an Ev[T], but you're providing it with EvInt and EvString. But what I thought at first, was that it's trying to say under the hood that it requires T, but you're providing it with Int and String. And I thought why this is happening, is probably because of different rules to type inference in classes and methods and other identifiers. For instance, if you change your code to:

class C[T](ev: Ev[T]) {
  // or use ev.asInstanceOf[Ev[_]]
  val id: Int = (ev: Ev[_]) match {
    case EvInt => 0
    case EvStr => 1
  }
}

Everything would work fine! Because you're basically telling the compiler there is no concrete T here.

Note that you wouldn't face the issue if you specify the type:

ev match {
  case _: EvInt.type => 0
  case _: EvString.type => 1
}

Also, instead of case objects (which at first extend their own type), if you use vals, the compilation error would go away:

val eInt: Ev[Int] = EvInt
val eStr: Ev[String] = EvString

val id: Int = ev match {
  case eInt => 0
  case eStr => 1
}

So in general, I think the compiler couldn't prove the relation between EvInt and EvString with Ev[*T*], and this problem of type inference was sort of common in scala 2. I'd say it's a bug, but I'm not confident about that.

Hope this helps!

like image 186
AminMal Avatar answered Nov 17 '25 22:11

AminMal



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!