Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference between type inference of method and class type parameters in pattern matching

Why does pattern matching work differently when type parameter comes from an enclosing method as opposed to an enclosing class? For example,

trait Base[T]
case class Derived(v: Int) extends Base[Int]

class Test[A] {
  def method(arg: Base[A]) = {
    arg match {
      case Derived(_) => 42
    }
  }
}

gives error

constructor cannot be instantiated to expected type;
 found   : A$A87.this.Derived
 required: A$A87.this.Base[A]
      case Derived(_) => 42
           ^

whilst it successfully compiles when A is method type parameter

class Test {
  def method[A](arg: Base[A]) = {
    arg match {
      case Derived(_) => 42
    }
  }
}

The question is based on Daniel's analysis, which I used to attempt to provide answer to similar question.

like image 771
Mario Galic Avatar asked Mar 05 '20 21:03

Mario Galic


1 Answers

I don't have the 100% complete answer, but I have a pointer that might be sufficient for you.

Scala compiler deals with GADTs (Generalized Algebraic Data Types) in a very particular way. Some cases are solved with special handling, some cases are unsolved. Dotty is trying to fill most of the holes, and it has already solved a lot of related issues, however there are still quite a few open ones as well.

Typical example of special GADT handling in Scala 2 compiler is very related to your use case. If we take a look at:

def method[A](arg: Base[A]) = {
  arg match {
    case Derived(_) => 42
  }
}

and we explicitly declare the return type to be A:

def method[A](arg: Base[A]): A 

it will compile just fine. Your IDE might complain, but the compiler will let it through. Method says it returns an A, but the pattern matching case evaluates into an Int, which theoretically shouldn't compile. However, special handling of GADTs in the compiler says it's fine, because in that particular pattern matching branch A has been "fixed" to be an Int (because we matched on Derived which is a Base[Int]).

Generic type parameter for the GADT (in our case A) has to be declared somewhere. And here's the interesting part - special compiler handling only works when it's declared as the type parameter of the enclosing method. If it's coming from a type member or a type parameter of the enclosing trait/class, it doesn't compile, as you witnessed yourself.

This is why I said it's not a 100% complete answer - I cannot point to a concrete place (such as official specification) which documents this properly. Sources on handling of GADTs in Scala come down to a couple of blogposts, which are great by the way, but if you want more than that you will have to dig into the compiler code yourself. I tried doing exactly that, and I think it comes down to this method, but if you really want to go deeper, you might want to ping someone more experienced with the Scala compiler codebase.

like image 62
slouc Avatar answered Sep 19 '22 20:09

slouc