monomorphisation in Haskell
Bernard James POPE
bjpop@cs.mu.OZ.AU
Fri, 10 Nov 2000 16:19:51 +1100 (EST)
Hi Henry and others,
I have sent this to Lee Naish and Toby Ord because they are working in
the debugger too and I think they will be interested in this
discussion.
> I'm not super familiar with Haskell, but I'm really surprised to hear that it
> has polymorphic recursion. Given the undecidability of type inference in
> this case, I assume that you must explicitly type definitions of such
> functions. Is this correct?
Yes, you must provide type signatures if you want polymorphic recursion.
This is a requirement of the Mycroft type-checking algorithm (not type
inference). Haskell uses Hindley Milner inference when no sigs are provided,
and Mycroft when they are provided.
Polymorphic recursion doesn't get used that often, typically only for
weird data-structures. I think Chris Okasaki has used PR in
his book Purely Functional Data Structures:
http://www.cs.columbia.edu/~cdo/papers.html#cup98
> Also I assume that Haskell does not have the value restriction of ML, and
> Stephen and I were talking relatively recently about how it is only this
> which makes monomorphisation possible.
Haskell is purely functional so the value restriction is not required.
> In Haskell you don't have any side
> effects, but still, how would you translate something like the following:
>
> val f: unit -> 'a -> 'a =
> fn () =>
> let fun loop n =
> if n = 0
> then ()
> else loop (n - 1)
> val _ = loop 10000000
> in fn x => x
> end
>
> val g: 'a -> 'a = f ()
> val _ = g "foo"
> val _ = g true
The equivalent Haskell code is:
f :: () -> a -> a
f = \() -> let loop n =
if n == 0
then ()
else loop (n - 1)
in loop 1000000 `seq` (\x -> x)
g :: a -> a
g = f ()
topLevel :: String
topLevel = g "foo" ++ show (g True)
The seq forces its left argument to evaluate to WHNF. This must be done b/c
Haskell is lazy, so normally the loop would never be run b/c its result
is not required.
> If you duplicate the definition of g for the two types it is applied to
> (string and bool) then you will have to run the loop action twice instead of
> just once.
This is true.
> Given that your use is for a debugger, I guess you could do this,
> but for a compiler the results would be rather catastrophic.
For the debugger we pay the cost of extra computation (sigh).
However, it is not as bad as you might think. With our debugger, we only
specialise when a type variable is instantiated with a functional type
(something with an arrow in it). So for the above example we do not call
different versions of g for the two types it is called in, and hence we only
run the loop once. Having said that, you could easily construct an example
where higher order arguments are used, and we would have to specialise apart.
Thus I should point out that in the debugger we are not obliged to always
specialise polymorphic functions, we only do so in selected cases. For a
compiler you might always have to do this. I suppose if you were to
relax the value restriction from ML then you could specialise only those
parts of the program that abided by the original restriction, anything else
would have to be generated as polymorphic code and hence used boxed
representations. The downside is that you would need to box and unbox values
all over the place and this would be messy and slow.
Monomorphisation is not possible in general (as far as we know) when you
encounter polymorphic recursion. I think this point is made in some of the
material that Stephen sent me.
So we can only debug the subset of Haskell that is restricted to monomorphic
recursion.
Regards,
Bernie.