[Haskell-cafe] universal quantification is to type instantiations as existential quantification is to what

Tillmann Rendel rendel at Mathematik.Uni-Marburg.de
Thu Aug 12 15:12:03 EDT 2010


Hi,

to understand forall and exists in types, I find it helpful to look at 
the terms which have such types.

Joshua Ball wrote:
> mapInt :: forall a. (Int -> a) -> [Int] -> [a]
> 
> I can instantiate that function over a type and get a beta-reduced
> version of the type
> 
> mapInt [String] :: (Int -> String) -> [Int] -> [String]

So mapInt could be defined with a upper-case lambda /\ which takes a type:

   mapInt = /\a -> ...

Of course, in Haskell, we don't have that lambda explicitly.

> The universal quantifier is basically a
> lambda, but it works at the type level instead of the value level.

Not quite. It is the /\ which is like \, except that \ takes a value, 
while /\ takes a type. Now, the type of

   /\ ...    is   forall ...,

and the type of

   \ ...     is   ... -> ... .

Therefore, forall is similar to ->. And indeed, both forall and -> 
describe terms which take arguments.

However, there are two differences between -> and forall. First, a term 
described by -> takes a value argument, while a term described by forall 
takes a type argument. And second, the result type of a term described 
by -> is independent from the argument taken, while the result type of a 
term described by forall may depend on the argument taken.

> My question is... what is the analog to an existential type?
> 
> mapInt :: exists a. (Int -> a) -> [Int] -> [a]

This mapInt could be defined with a version of the pair constructor (,) 
which accepts a type in the first component.

   mapInt = (SomeType, ...)

Note that the ... in that definition need to have the following type:

   (Int -> SomeType) -> [Int] -> SomeType

So exists is similar to (,). Both exists and (,) describe terms which 
represent pairs.

And again, there are two differences between (,) and exists. First, a 
term described by (,) represents a pair of two values, while a term 
described by exists represents a pair of a type and a value. And second, 
the type of the second component of a term described by (,) is 
independent of the first component, while the type of the second 
component of a term described by exists may depend on the first component.

> 1. If I can "instantiate" variables in a universal quantifier, what is
> the corresponding word for variables in an existential quantifier?

If foo has an universal type, the caller of foo can choose a type to be 
used by foo's body by instantiating the type variable. That type 
variable is available to foo's body.

If foo has an existential type, the body of foo can choose a type to be 
available to the caller of foo by creating an existential pair. That 
type is available to foo's caller by unpacking the existential pair, for 
example, by pattern matching.

> 2. If type instantiation of a universally quantified variable is
> beta-reduction, what happens when existentially quantified variables
> are [insert answer to question 1]?

beta-reduction happens when the type application meets the lambda 
abstraction:

   (/\ a -> b) [T]   ~>   b [a := T]

The corresponding reduction for existentials happens when the unpacking 
meets the construction of the existential pair.

   case (T, t) of (a, x) -> b   ~>   b [a := T; x := t]

I don't know how it is called.

BTW, note how the type variable a is bound in the pattern of the case 
expression, so it scopes over b. But the type of b is also the type of 
the overall case expression. Therefore, if the type of b would mention 
the type variable a, so would the overall type of the case expression, 
and then, the type variable a would be used out of scope. This is why 
GHC complains about "escaping type variables" sometimes.

> 3. Is there any analogue to existential quantifiers in the value
> world? "Forall" is to "lambda" as "exists" is to what?

Forall is to lambda as exists is to (,), as discussed above.

HTH,
   Tillmann


More information about the Haskell-Cafe mailing list