Existential types don't infer for invariant nested data structures


#1

Hi all

case class A[T](t:T)
case class WrapperA[T](a:A[T], t: T)

val a: A[_] = A(4)
WrapperA(a, 2)

fails with the following error

<console>:15: error: type mismatch;
 found   : A[_$1] where type _$1
 required: A[Any]
Note: _$1 <: Any, but class A is invariant in type T.
You may wish to define T as +T instead. (SLS 4.5)
       WrapperA(a, 2)

how can I make it work?

  • metaquestion:
    does the scala community prefer to post questions like this in contributors.scala-lang.org or at stackoverflow.com?
    I had a couple of bad experiences on receiving no question at all on scala questions on stackoverflow (after months), but my scala contributors experience was amazing so far, immediate help)

#2

WrapperA(a, 2)

I don’t think that can work. if T is inferred to Any then A[_] is not a subtype of A[T], if T is inferred to “the same wildcard type that is in the type of a”, then Int is not a subtype of T, so how could this expression typecheck ?


#3

I just was able to circumvent this, by explicitly storing the existential type. The following compiles and runs

type Exists = T forSome {type T}
val a2: A[Exists] = A(4)
WrapperA(a2, 2)

and infers res6: WrapperA[Exists] = WrapperA(A(4),2) for the last expression


#4

I don’t recommend using existentials for this.

If it’s indeed a wrapper, and you don’t care what’s in it, why not make it covariant?

class A[+T](t:T)
class WrapperA[+T](a: A[T], t: T)

val a2: A[Any] = new A(4)
new WrapperA(a2, 2)

#5

while WrapperA is a wrapper, A is not

in the true example I am facing, both are in fact invariant and not under my control

  • A === org.apache.spark.ml.param.Param
  • WrapperA === org.apache.spark.ml.param.ParamPair

#6

Ok, then I would manage the scope of the existential using pattern matching or maybe by factoring out the code to a polymorphic method. The type alias trick with the existential won’t be portable to dotty, unless I’m mistaken.


#7

can you give an example, who you could fix my original problem with patternmatching or polymorphic methods?


#8

Looks like I can’t :slight_smile: Normally, you can introduce a type variable in a pattern to “capture the wildcard” (as Java would say) or “open the existential”, which allows you to name the existentially quantified type without saying what it is. This will help when type inference can’t infer the existential.

That’s not actually what’s happening here. There’s simply no connection between where you produce the A and then put it in the WrapperA.

In other words, there’s no way to tell the type checker that a's type is related to Int, which is required for WrapperA(a, 2) to type check.

So, this doesn’t work:

val a: A[_] = A(4)

a match { 
  case a: A[t] => WrapperA[t](a, 2)
}

This does (but I don’t know if this fits your requirements):

val at: (A[t], t) forSome {type t} = (A(4), 2)

at match {  case (a, t) => WrapperA(a, t) } // inside the scope of the match, 
// the type checker will introduce one existential that it can use 
// for both `a` and `t`