bug icon indicating copy to clipboard operation
bug copied to clipboard

Existential type not stable

Open TomasMikula opened this issue 8 years ago • 3 comments

When having an existentially parameterized value val foo: Foo[_], I would expect the type argument, although unknown, be stable among all usages of foo. This is clearly not the case:

class Foo[A]

object Main {
  val foo: Foo[_] = new Foo[String]

  // Test 1
  def f[A](foo1: Foo[A], foo2: Foo[A]): Unit = ()
  f(foo, foo) // error

  // Test 2
  case class Bar[A](foo: Foo[A]) {
    def f(foo: Foo[A]): Unit = ()
  }
  Bar(foo).f(foo) // error
}

This is the compiler output:

$ ~/scala-2.12.2/bin/scalac main.scala 
main.scala:8: error: type mismatch;
 found   : Foo[_$1] where type _$1
 required: Foo[Any]
Note: _$1 <: Any, but class Foo is invariant in type A.
You may wish to define A as +A instead. (SLS 4.5)
  f(foo, foo) // error
    ^
main.scala:8: error: type mismatch;
 found   : Foo[_$1] where type _$1
 required: Foo[Any]
Note: _$1 <: Any, but class Foo is invariant in type A.
You may wish to define A as +A instead. (SLS 4.5)
  f(foo, foo) // error
         ^
main.scala:14: error: type mismatch;
 found   : Foo[(some other)_$1(in value <local Main>)] where type (some other)_$1(in value <local Main>)
 required: Foo[_$1(in value <local Main>)] where type _$1(in value <local Main>)
  Bar(foo).f(foo) // error
             ^
three errors found

TomasMikula avatar Apr 22 '17 04:04 TomasMikula

foo being effectively final is essential to your example. Anyhow, just as an FYI, it is possible to capture the skolem in a fairly general way,

scala> def withFoo[T, U](foo: Foo[T])(f: Foo[T] => U): U = f(foo)
withFoo: [T, U](foo: Foo[T])(f: Foo[T] => U)U

scala> withFoo(foo)(fooT => f(fooT, fooT))

scala> withFoo(foo)(fooT => Bar(fooT).f(fooT))

Your suggestion amounts to saying that this transformation should be automatic where the existential value is effectively final.

milessabin avatar Apr 25 '17 07:04 milessabin

Thanks for the withFoo suggestion, @milessabin.

Just to clarify, by "effectively final", do you mean what scalac calls a "stable identifier", as in this error message?

scala> class Foo { type A }
defined class Foo

scala> def foo: Foo = new Foo
foo: Foo

scala> List[foo.A]()
<console>:13: error: stable identifier required, but foo found.
       List[foo.A]()

TomasMikula avatar Apr 25 '17 20:04 TomasMikula

Or you can use a simple match instead of withFoo:

class Foo[A]

object Main {
  val foo: Foo[_] = new Foo[String]

  // Test 1
  def f[A](foo1: Foo[A], foo2: Foo[A]): Unit = ()
  foo match { case foo1 => f(foo1, foo1) } // No error

  // Test 2
  case class Bar[A](foo: Foo[A]) {
    def f(foo: Foo[A]): Unit = ()
  }
  foo match { case foo1 => Bar(foo1).f(foo1) } // No error
}

Atry avatar Apr 26 '17 02:04 Atry