Shrink the Prelude

People sometimes complain that the Prelude steals lots of good names from the user. Think of 'map' or (+). Yes, most of the time we want these names to have a standard interpretation, but occasionally it is useful to be able to redefine them to mean something else. For instance, there are several proposals to change the prelude's numeric class hierarchy into something more flexible involving rings, groups, and so on.

But it is tedious if the user of such a proposed replacement for the Prelude must explicitly hide the standard prelude in every importing module.

Thus it might be useful to trim the Prelude down to the bare minimum possible. Most users of e.g. list functions would then need to import Data.List, users of numeric functions would need to import Data.Numeric, and so on. Of course, some users (e.g. university teachers) might want to collect a bunch of utility libraries into a single import resembling the current Prelude. But the point is that they could choose the features they want to expose to students, and hide those they want to avoid as well. For instance, there are certainly some teachers who would like to be able to ignore the class overloading system altogether at the beginning, then perhaps introduce the concept later on, once the basics have been covered.

Examples of proposals for restructuring the Prelude

  • revise the Numeric class hierarchy, e.g. collapse classes RealFrac, Fractional, and Floating.
  • make String into a class rather than a type, with instances for [Char], PackedString, etc

Concrete proposals for deciding what to remove

  1. Remove entities that clash with other common libraries, which would otherwise require import Prelude hiding. For example, a function called catch is also exported by Control.Exception, take/drop are also exported by Data.Sequence. A list of these can easily be constructed.
  2. Remove entities that are used in fewer than N% of modules (measure as much code as we can get our hands on, choose some appropriate N).
  3. Decide what to keep.
  4. Remove the implicit import of the Prelude in its entirety. Where a syntactic desugaring rule currently uses an entity from the Prelude, the new interpretation is that it uses whatever binding of that entity is in scope - if there is no such entity in scope, it is an error. For compatibility, the current Prelude can continue to be made available as an explicit import, so that the only change required to fix existing code is import Prelude. Indeed, an implementation may choose to provide a compile-time option like -fimplicit-prelude, the inversion of ghc's -fno-implicit-prelude, to avoid the need to change old code at all.

For the record I (Simon M.) support doing at the very least (1), and I'm interested in measuring code along the lines of (2) so that we can find out what things are hardly ever used. (3) is rather less conservative. (4) is Malcolm's idea.


At the moment this is an idea for a proposal, rather than a proposal. It needs to be made concrete before we can act on it.

On the other hand, any firm proposal in this area would cost a significant amount of work. Some indication of general positivity or negativity towards the idea would help us know whether it is worth expending effort on devising some definite proposals.

Data points

Language support

The following names are used by the language definition:

Bool (True, False), Char, String, Integer, Rational, Ratio, IO,
Eq ((==)), Ord ((>=)),
Enum (enumFrom, enumFromThen, enumFromTo, enumFromThenTo),
Num (fromInteger, negate, (-)), Fractional (fromRational),
Monad ((>>=), (>>), fail),
undefined, error, (%), concatMap

Defaulting uses Double and all classes defined in standard libraries.

Derived instances use the classes Eq, Ord, Enum, Bounded, Show and Read, which refer to Ordering, Int, ShowS and ReadS.

The ForeignFunctionInterface uses

Char, Int, Double, Float, Bool,
Int8, Int16, Int32, Int64, Word8, Word16, Word32, Word64,
Ptr, FunPtr, StablePtr


The following Prelude functions are re-exported by other Haskell 98 modules:

map, (++), concat, filter, head, last, tail, init, null, length, (!!), foldl, foldl1, scanl, scanl1, foldr, foldr1, scanr, scanr1, iterate, repeat, replicate, cycle, take, drop, splitAt, takeWhile, dropWhile, span, break, lines, words, unlines, unwords, reverse, and, or, any, all, elem, notElem, lookup, sum, product, maximum, minimum, concatMap, zip, zip3, zipWith, zipWith3, unzip, unzip3
ioError, userError, catch, interact, putChar, putStr, putStrLn, print, getChar, getLine, getContents, readFile, writeFile, appendFile, readIO, readLn
mapM, mapM_, sequence, sequence_, (≤<)

Concrete proposal for a minimal Prelude

Here is a concrete proposal for a truly minimal Prelude. Let the Prelude itself contain only entities that relate purely to functions - no other datatypes.

module Prelude
    ( (->)
    , (.)
    , ($)
    , ($!)
    , flip
    , id
    , const
    , error
    , undefined
    , seq
    , asTypeOf

(if we have scoped type variables, do we still need asTypeOf?)

Everything else that is currently in the Haskell'98 Prelude is re-distributed across a variety of small modules. Where a syntactic desugaring rule currently uses an entity from the Prelude, the new interpretation is that it uses whatever binding of that entity is in scope - if there is no such entity in scope, it is an error. For compatibility, we define a wrapper module called Prelude.Haskell98 which re-exports the original Haskell'98 Prelude:

module Prelude.Haskell98
    ( module Prelude
    , module Prelude.Num
    , module Prelude.Comparison
    , module Prelude.Monad
    , module Prelude.List
    , module Prelude.Maybe
    , module Prelude.Either
    , module Prelude.Tuple
    , module Prelude.IO
    , module Prelude.Text

The rules for implicit import of the Prelude are now as follows. The new minimal Prelude is imported implicitly unless there is an explicit mention of it in an import decl. The larger Prelude.Haskell98 is implicitly imported only when the module Main where header is omitted from a main program module. This ensures that extremely simple programs continue to work without needing to add a new import, but in all other cases, one must explicitly choose whether to use the Prelude.Haskell98, or something else. We expect implementations might want to provide a flag like -fimplicit-prelude to automatically add an import of Prelude.Haskell98 to legacy modules.

Here are the individual fragments of the re-organised Prelude:

module Prelude.Num
    ( Natural(..)  -- new in h-prime?
    , Int(..)
    , Int8(..)     -- previously in Data.Int
    , Int16(..)
    , Int32(..)
    , Int64(..)
    , Word8(..)    -- previously in Data.Word
    , Word16(..)
    , Word32(..)
    , Word64(..)
    , Integer(..)
    , Float(..)
    , Double(..)
    , Rational
    , class Integral(..)
    , class Num(..)
    , class Fractional(..)
    , class Real(..)
    , class RealFrac(..)
    , class Floating(..)
    , class RealFloat(..)
    , gcd, lcm
    , fromIntegral, realToFrac
    , numericEnumFrom, numericEnumFromTo, numericEnumFromThen
    , numericEnumFromThenTo
    , (^), (^^), (%)
    , even, odd, subtract

module Prelude.Comparison
    ( Bool(..)
    , Ordering(..)
    , class Eq(..)
    , class Ord(..)
    , class Enum(..)
    , class Bounded(..)
    , otherwise
    , (&&), (||), not, until

module Prelude.Monad
    ( class Functor(..)
    , class Monad(..)
    , mapM, mapM_, sequence, sequence_, (=<<)

module Prelude.List
    ( [](..)
    , all, and, any, (++), break, concat, concatMap, cycle, drop, dropWhile
    , elem, filter, foldl, foldl1, foldr, foldr1, head, (!!), init, iterate
    , last, length, lines, lookup, map, maximum, minimum, notElem, null
    , or, product, repeat, replicate, reverse, scanl, scanl1, scanr, scanr1
    , span, splitAt, sum, tail, take, takeWhile, unlines, unwords, words

module Prelude.Maybe
    ( Maybe(..)
    , maybe

module Prelude.Either
    ( Either(..)
    , either

module Prelude.Tuple
    ( ()(..)
    , (,)(..)
    , (,,)(..)
    , (,,,)(..)
    , (,,,,)(..)
    , (,,,,,)(..)
    , (,,,,,,)(..)
    , (,,,,,,,)(..)
    , (,,,,,,,,)(..)
    , (,,,,,,,,,)(..)
    , (,,,,,,,,,,)(..)
    , (,,,,,,,,,,,)(..)
    , (,,,,,,,,,,,,)(..)
    , (,,,,,,,,,,,,,)(..)
    , (,,,,,,,,,,,,,,)(..)
    , fst, snd
    , unzip, unzip3, zip, zip3, zipWith, zipWith3
    , curry, uncurry

module Prelude.IO
    ( IO
    , IOError(..)
    , FilePath
    , ioError, userError, catch
    , print
    , putChar, putStr, putStrLn
    , getChar, getLine, getContents, interact
    , readFile, writeFile, appendFile, readIO, readLn

module Prelude.Text
    ( Char(..)
    , String
    , class Read(..)
    , class Show(..)
    , ReadS
    , ShowS
    , read, reads, readParen, lex
    , shows, showString, showParen, showChar
Last modified 12 years ago Last modified on Mar 25, 2007 9:27:42 PM