Usually, all type variables in Haskell are implicitly universally quantified in the farthest type field. RankNTypes allows the universal forall quantifier to appear nested, for example. type forall a b. (a -> a) -> b -> b forall a b. (a -> a) -> b -> b very different from forall b. (forall a. a -> a) -> b -> b forall b. (forall a. a -> a) -> b -> b .
There is a sense in which the types on the left side of the function arrow are logically βdeniedβ, in approximately the same sense that (->) is a logical implication. Logically universal and existential quantifiers are related by the De Morgan duality: (βx. P (x)) is equivalent to Β¬ (βx. Β¬P (x)), or, in other words, "there is x such that P (x)" corresponds to " , this does not mean that for all x P (x) is false. "
So, forall to the left of the arrow function is βdeniedβ and behaves as existential. If you put all this to the left of the other arrow of the function, it will be a double negation and again will look like a universal quantifier, modulo some details, detailed.
The same idea of ββnegation also applies to values, therefore, for encoding of type exists x. x exists x. x we want:
forall x. in contravariant (negative) position- the value of this type
x in the covariant (positive) position.
Since the value must be within the scope of the quantifier, our only choice is double negation - CPS conversion, basically. To avoid restrictions otherwise, we will then quantify the type to the right of the function arrows. So there exists x. x exists x. x translates to forall r. (forall x. x -> r) -> r forall r. (forall x. x -> r) -> r . Compare the placement of types and quantifiers here with the above requirements to make sure it meets the requirements.
In more operational terms, this means that if a function with the type indicated above is given, because we give it a function with a universal quantized argument type, it can apply this function to any type x that it likes, and since it has no other way to get a value of type r , we know that it will apply this function to something. Thus, x will refer to some type, but we do not know that - in essence, the essence of existential quantification.
In more practical, everyday conditions, any universal quantitative variable of a type can be regarded as existential if you look at it from the βother sideβ of a function type. Since unification performed as part of type inference is beyond the scope of quantization, you may sometimes find yourself in a situation where the GHC will need to combine a type variable in the outer region with a quantified type from the nested region, i.e. how do you get compiler errors about type acceleration and skolems and whatnot, the latter (I suppose) is associated with the normal form of Skolem .
The way this relates to data types using existences is that although you can declare a type of type:
data Exists = forall a. Exists a
This is an existential type, to get an "existential type", you need to expand it according to the pattern:
unexist :: Exists -> r unexist (Exists x) = foo x
But if you consider what type foo should be in this definition, you will get something like forall a. a -> r forall a. a -> r , which is equivalent to the CPS style encoding above. There is a close connection between CPS conversions and church coding of data types, so the CPS form can also be seen as an updated version of pattern matching.
Finally, by connecting things with logic - starting from where the term "existential quantifier" comes from, note that if you think that it is left from the arrow as a negation and a kind of squint to ignore forall r. ... forall r. ... CPS cruft, these existential type encodings exactly coincide with the dualized De Morgan form Β¬ (βx. Β¬P (x)), which was the starting point. So all these are just different ways to look at the same concept.