Primitive types and Prelude shenanigans

Simon Peyton-Jones
Wed, 14 Feb 2001 14:19:39 -0800

| On Mon, Feb 12, 2001 at 02:38:25PM -0800, William Lee Irwin III wrote:
| > I had in mind looking within the compiler, actually. Where in the
| > compiler? It's a big program, it might take me a while to do an
| > uninformed search. I've peeked around a little bit and not gotten
| > anywhere.
| If anyone else is pursuing thoughts along the same lines as I 
| am (and I
| have suspicions), TysWiredIn.lhs appears quite relevant to the set of
| primitive data types, though there is no obvious connection to the
| module issue (PrelBase.Bool vs. Foo.Bool). PrelMods.lhs 
| appears to shed
| more light on that issue in particular. $TOP/ghc/compiler/prelude/ was
| the gold mine I encountered.

Perhaps I should add something here.

I'm very sympathetic to the idea of making it possible to do entirely
without the standard Prelude, and to substitute a Prelude of one's own.

The most immediate and painful stumbling block in Haskell 98 is that numeric
like 3, turn into (Prelude.fromInt 3), where "Prelude.fromInt" really means
"the fromInt from the standard Prelude" regardless of whether the standard
Prelude is imported scope.

Some while ago I modified GHC to have an extra runtime flag to let you
this behaviour.  The effect was that 3 turns into simply (fromInt 3), and
"fromInt" means "whatever fromInt is in scope".  The same thing happens for
	- numeric patterns
	- n+k patterns (the subtraction is whatever is in scope)
	- negation (you get whatever "negate" is in scope, not

(Of course, this is not Haskell 98 behaviour.)   I think I managed to forget
to tell anyone of this flag.  And to my surprise I can't find it any more!
But several changes I made to make it easy are still there, so I'll
it shortly.  That should make it easy to define a new numeric class

So much for numerics.  It's much less obvious what to do about booleans.
Of course, you can always define your own Bool type.  But we're going to
have to change the type that if-then-else uses, and presumably guards too.
Take if-then-else.  Currently it desugars to 
	case e of
	  True -> then-expr
	  False -> else-expr
but your new boolean might not have two constructors.  So maybe we should 
simply assume a function 	
	if :: Bool -> a -> a -> a
and use that for both if-then-else and guards....  I wonder what else?
For example, can we assume that
	f x | otherwise = e
is equivalent to
	f x = e
That is, "otherwise" is a guard that is equivalent to the boolean "true"
("otherwise" might be bound to something else if you import a non-std
If we don't assume this, we may generate rather bizarre code:
	f x y | x==y = e1
		| otherwise = e2

	f x y = if (x==y) e1 (if otherwise e2 (error "non-exhaustive
patterns for f"))

And we'll get warnings from the pattern-match compiler.  So perhaps we
guarantee that (if otherwise e1 e2) = e1.  

You may say that's obvious, but the point is that we have to specify what
can be assumed about an alien Prelude.

Matters get even more tricky if you want to define your own lists.  
There's quite a lot of built-in syntax for lists, and type checking that
goes with it.  Last time I thought about it, it made my head hurt.
Tuples are even worse, because they constitute an infinite family.

The bottom line is this.
  a) It's desirable to be able to substitute a new prelude
  b) It's not obvious exactly what that should mean
  c) And it may not be straightforward to implement

It's always hard to know how to deploy finite design-and-implementation
resources.  Is this stuff important to a lot of people?  
If you guys can come up with a precise specification for (b), I'll
think hard about how hard (c) really is.