[Haskell-cafe] Re: Aren't type system extensions fun? [Further analysis]

Philippa Cowderoy flippa at flippac.org
Tue May 27 18:50:29 EDT 2008


Warning for Andrew: this post explains a new-to-you typed lambda calculus 
and a significant part of the innards of Hindley-Milner typing in order to 
answer your questions. Expect to bang your head a little!

On Tue, 27 May 2008, Andrew Coppin wrote:

> - A function starts out with a polymorphic type, such as 'x -> x'.
> 

Which is the same as "forall x. x -> x".

> - Each time the function is called, all the type variables have to be locked
> down to real types such as 'Int' or 'Either (Maybe (IO Int)) String' or
> something.
> 

Yep, in fact in the explicitly-typed calculus commonly used as a model for 
rank-n polymorphism (System F) there are explicit type lambdas and type 
applications that do exactly this. Using /\ for a type lambda and [term 
type] for type applications, id might be written thus:

id = /\t.(\x::t->x)

and called thus:

[id Int] 1

> - By the above, any function passed to a high-order function has to have all
> its type variables locked down, yielding a completely monomorphic function.
> 

Yep.

> - The "forall" hack somehow convinces the type checker to not lock down some
> of the type variables. In this way, the type variables relating to a function
> can remain unlocked until we reach each of the call sites. This allows the
> variables to be locked to different types at each site [exactly as they would
> be if the function were top-level rather than an argument].
> 

Not a hack. If there is a hack, it's in /not/ including a forall for 
rank-1 types. Foralls correspond to type lambdas in the same way that ->s 
correspond to ordinary lambdas.

> - Why are top-level variables and function arguments treated differently by
> the type system?

The big difference is between lambdas and binding groups (as in let, 
top-level etc). With the binding group, any constraints on a value's type 
will be found within its scope - so the type can be 'generalised' there, 
putting a forall around it. The same isn't true of lambdas, and so their 
parameters can only be polymorphic given an appropriate type annotation. 
For extra confusion, type variables are themselves monomorphic[1].

> - Why do I have to manually tell the type checker something that should be
> self-evident?

It isn't - the general case is in fact undecidable.

> - Why do all type variables need to be locked down at each call site in the
> first place?

Find an alternative model for parametric polymorphism that works!

Note that 'not locking down' is equivalent to locking down to parameters 
you took from elsewhere. As such, if you stick to rank-1 types you never 
actually notice the difference - whereas it makes the type system an awful 
lot easier to understand.

> - Why is this virtually never a problem in real Haskell programs?

I wouldn't go so far as to say virtually never, I've run into it a fair 
few times - but normally until you're trying to do pretty generalised 
stuff it's just not necessary.

> - Is there ever a situation where the unhacked type system behaviour is
> actually desirably?

There are plenty of situations where a rank-1 type is the correct type. If 
you give id a rank-2 type, it's more specific - just as if you typed it 
Int->Int.

> - Why can't the type system automatically detect where polymorphism is
> required?

Because there's often more than one possible type for a term, without a 
'most general' type.

> - Does the existence of these anomolies indicate that Haskell's entire type
> system is fundamentally flawed and needs to be radically altered - or
> completely redesigned?
> 

About as much as the undecidability of the halting problem and the 
incompleteness theory suggest our understanding of computation and logic 
is fundamentally flawed - which is to say, not at all.

> The key problem seems to be that the GHC manual assumes that anybody reading
> it will know what "universal quantification" actually means. Unfortunately,
> neither I nor any other human being I'm aware of has the slightest clue what
> that means. 

Guess nobody on this list's human, huh?

It's really not the GHC manual's job to teach you the language extensions 
from scratch any more than it should teach Haskell 98 from scratch. I 
guess the real question is where the relevant community documentation is, 
something I have to admit to not having needed to check.

>  existential quantification = "this is true for SOMETHING"
>  universal quantification = "this is true for EVERYTHING"
> 

The type "forall x. x -> x" is "for all types x, x -> x". As in, "for all 
types x, (\y -> y) :: x -> x".

[1] Actually this is no longer quite true since GHC now supports 
impredicative polymorphism in which type variables can be instantiated 
with polymorphic types. But please ignore that for now?

-- 
flippa at flippac.org

'In Ankh-Morpork even the shit have a street to itself...
 Truly this is a land of opportunity.' - Detritus, Men at Arms


More information about the Haskell-Cafe mailing list