Scala type checker does not recognize types in scripts with abstract class dependent

Define a trait with an abstract class

object Outer { trait X { type T val empty: T } 

Now we can make an instance of it:

  val x = new X { type T = Int val empty = 42 } 

Scala now recognizes x.empty is Int :

  def xEmptyIsInt = x.empty: Int 

Now define another class

  case class Y(x: X) extends X { type T = xT val empty = x.empty } 

and enter its instance

  val y = Y(x) 

But now Scala cannot conclude that y.empty is of type Int . Following

  def yEmptyIsInt = y.empty: Int 

now produces an error message:

 error: type mismatch; found : yxT required: Int y.empty : Int 

Why is this so? Is this a scala bug?

We can mitigate this problem by introducing parameters in the case class:

  case class Z[U](x: X { type T = U }) extends X { type T = U val empty = x.empty } 

Then it works again

  val z = Z(x) def zEmptyIsInt: Int = z.empty 

But we should always mention all types inside X on the call site site. This should ideally be an implementation detail that leads to the following approach:

  case class A[U <: X](z: U) extends X { type T = zT val empty = z.empty } 

It also alleviates the problem.

  val a = A(x) def aEmptyIsInt: Int = a.empty } 

So, in summary, my questions are: why doesn't a simple case work? Is this a valid workaround? What other problems may arise when we follow one of two approaches? Is there a better approach?

+5
source share
1 answer

You reused x for different things, so from here I will call the object created by val x "instance x" and x: X used in the class parameter Y x.

"Instance x" is an anonymous subclass of trait X with specific members that overlap the abstract elements of trait X As a subclass of x you can pass it to the constructor for case class Y , and it will be accepted with pleasure, since it is x as a subclass.

It seems to me that you expect case class Y then check at runtime to see if the instance of x that it passed passed overrides the x members and generated an instance of Y whose members are of different types depending on what was passed.

This is categorically not how Scala works, and the goal of its (static) type will largely win. For example, you cannot use anything with Y.empty without reflection at run time, since it can be of any type at all, and at this point you should use only a system of dynamic type. If you need the advantages of parametricity, free theorems that do not require reflection, etc., then you should adhere to statically defined types (with a small exception for comparison with the sample). And this is what Scala does.

What actually happens is that you told constructor Y that the parameter x is x , and therefore the compiler statically determines that x.empty is of type x.empty , which is an abstract type of T Scala's conclusion does not fail; your types are actually incompatible.

Separately, this has nothing to do with path-dependent types. Here is a good walkthrough , but, in short, path-dependent types are tied to their parent instance, are not dynamically determined at runtime.

+1
source

All Articles