0

I did a quick experiment to understand the nature of path-dependent type feature of scala:

  trait SS {

    type II
  }

  class SS1() extends SS {}

  def sameType[T1, T2](implicit ev: T1 =:= T2): Unit = {}

// experiments start here

    val a = new SS1()

    val b = new SS1()

    val c = a

    sameType[a.II, a.II]

    assertDoesNotCompile(
      """
        |sameType[a.II, b.II]
        |""".stripMargin
    ) // of course it happens

So far so good? Until I type in the next line:

    sameType[a.II, c.II] 

At which point the compiler gave the following error:

[Error] .../PathDependentType/ProveSameType.scala:32: Cannot prove that a.II =:= c.II.
one error found

despite that constant a & c always refer to 2 completely identical objects. So why scala compiler choose not to prove it? Does this implies that a & c refers to 2 different 'paths', and such behaviour is expected for practicality?

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
tribbloid
  • 4,026
  • 14
  • 64
  • 103
  • 1
    My guess is that `c` is inferred to `SS1` rather than `SS1 { override type II = a.II }` and the compiler cannot prove they're the same because you haven't defined the type `II` anywhere – user Jun 06 '20 at 19:26

1 Answers1

3

It's absolutely correct behavior. Reference equality of a and c is irrelevant. Path-dependent types a.II and c.II are different.

a.II, c.II are shorthands for types a.type#II, c.type#II and singleton types a.type, c.type are different too.

Paths https://scala-lang.org/files/archive/spec/2.13/03-types.html#paths

Equivalence of types https://scala-lang.org/files/archive/spec/2.13/03-types.html#equivalence

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • 3
    Note that you can, of course, declare `val c: a.type = a` and have this work. Scala just chooses not to infer singleton types in this case. Note that `a.type#II` is itself shorthand for `v.II forSome { val v: a.type }` (as all `#` projections are). – HTNW Jun 06 '20 at 20:01
  • @HTNW *Note that you can, of course, declare `val c: a.type = a` and have this work.* Yeah, quote from spec: "If a path `` has a singleton type `.type`, then `.type ≡.type`." https://scala-lang.org/files/archive/spec/2.13/03-types.html#equivalence – Dmytro Mitin Jun 06 '20 at 20:10
  • @HTNW *Note that `a.type#II` is itself shorthand for `v.II forSome { val v: a.type }`* I can prove this in the following way: `(v.II forSome { val v: a.type }) ≡ (v.type#II forSome { val v: a.type }) ≡ (T#II forSome { type T <: a.type with Singleton }) ≡ (T#II forSome { type T <: a.type }) ≡ a.type#II ≡ a.II`. Do you know how to prove this shorter? – Dmytro Mitin Jun 06 '20 at 22:01
  • @HTNW *as all `#` projections are* Why do you say that all projections are existentials? Is `SS#II` in `trait SS { type II }`? `SS#II` is a supertype of `x.II forSome {val x: SS}`. – Dmytro Mitin Jun 06 '20 at 22:04
  • 1
    Oh, it appears I was mistaken... I think type projections are *supposed to be* equivalent to the existential forms but for some reason or another they're not. At least, that's what I gathered from the discussions that resulted in them both being removed from Dotty. It's a useful conceptual tool, anyway, since I don't think you can actually construct an `SS#II` that can't be described as a `s.II forSome { val s: SS }`. – HTNW Jun 06 '20 at 22:23
  • Yep. just tried your suggestion and it indeed works. Won't trust type inference blindly from now on :) It was probably written long before path-dependent type becomes a thing – tribbloid Jun 20 '20 at 22:08
  • Sorry the type inference is correct and totally trustworthy, if it always refine to singleton type many inferred private functions can't be overriden. In this case, choosing the most specific type is wrong – tribbloid Jun 26 '20 at 00:32