29
votes

Before Haskell 98, there were Haskell 1.0 through 1.4. It's pretty interesting to see the development throughout the years, as features were added to the earliest versions of standardized Haskell.

For instance, the do-notation was first standardized by Haskell 1.3 (published 1996-05-01). In the Prelude, we find the following definitions (page 87):

-- Monadic classes

class  Functor f  where
    map         :: (a -> b) -> f a -> f b

class  Monad m  where
    (>>=)       :: m a -> (a -> m b) -> m b
    (>>)        :: m a -> m b -> m b
    return      :: a -> m a

    m >> k      = m >>= \_ -> k

class  (Monad m) => MonadZero m  where
    zero        :: m a

class  (MonadZero m) => MonadPlus m where
    (++)        :: m a -> m a -> m a

The same definitions are found in Haskell 1.4. I do have a few problems with this (e.g. the MonadPlus reform hasn't happened here yet), but overall, it is a very nice definition.

This is very different from Haskell 98, where the following definition is found:

-- Monadic classes


class  Functor f  where
    fmap              :: (a -> b) -> f a -> f b


class  Monad m  where
    (>>=)  :: m a -> (a -> m b) -> m b
    (>>)   :: m a -> m b -> m b
    return :: a -> m a
    fail   :: String -> m a

        -- Minimal complete definition:
        --      (>>=), return
    m >> k  =  m >>= \_ -> k
    fail s  = error s

This is also the definition in Haskell 2010. I have the following problems with this definition:

  • MonadZero and MonadPlus are gone. They were useful classes.
  • In case of a pattern match failure in a do-notation...

    • Haskell 1.3 uses zero. The Left Zero law applies (zero >>= k = zero), so you know what's supposed to happen.
    • Haskell 98 uses fail msg, where msg is compiler-generated in case of GHC. Anything can happen, no guarantees about its semantics. Therefore, it's not much of a function for users. As a consequence, the behaviour of pattern match failures in Haskell 98's do-notation is unpredictable!
  • Names are less general (e.g. map vs. fmap). Not a big problem, but it's a thorn in my eye.


All in all, I think these changes weren't for the best. In fact, I think they were a step backwards from Haskell 1.4. Why were these things changed for Haskell 98, and why in this way?


As an aside, I can imagine the following defenses:

  • "fail allows for locating errors." Only for programmers, and only at runtime. The (unportable!) error message is not exactly something you want to parse. If you really care about it, you should track it explicitly. We now have Control.Failure from the failure package, which does a much better job at this (failure x behaves mostly like zero).
  • "Having too many classes makes development and use too hard." Having too few classes breaks their laws, and those laws are just as important as types.
  • "Instance-restricted functions are easier to learn." Then why isn't there a SimplePrelude instead, with most of the classes removed? It's only one magical declaration away for students, they can manage that much. (Perhaps {-# LANGUAGE RebindableSyntax #-} is needed too, but again, students are very good at copy-pasting stuff.)
  • "Instance-restricted functions make errors more readable." I use fmap much more often than map, so why not map and listMap instead?
1
One reason map got renamed to fmap and map specialised to lists is that the error messages you get when mapping over lists are much more newbie-friendly that way. Imagine being new to Haskell (or programming in general) and just wanting to change values in a list and you get an error message about No instance for (Functor f0)!kqr
I thought I refuted that in my aside, twice: newbies could use SimplePrelude (having map = listMap), more advanced users could opt to use listMap instead of map.user824425
I just wanted to point out the newbie perspective on it, since you didn't mention that specifically. The problem with your suggestions are that they make the experts the demographic that is likely to know about the newbie-friendly side of Haskell and the newbies will be struggling to understand the default expert side of Haskell – quite the opposite of what one wants in an easy to learn language! The reverse of what you are suggesting already exists in the form of basic-prelude.kqr

1 Answers

24
votes

Why were these things changed for Haskell 98, and why in this way?

Haskell 98 involves a lot of simplification to the language (much of which has since been reversed). The goal was to improve Haskell as a teaching language, and to make relatively conservative choices.

See e.g.

We regarded Haskell 98 as a reasonably conservative design. For example, by that time multi-parameter type classes were being widely used, but Haskell 98 only has single-parameter type classes (Peyton Jones et al., 1997).

In: History of Haskell

And:

Haskell 98 will by no means be the last revision of Haskell. On the contrary, we design it knowing that new language extensions (multi-parameter type classes, universal and existential quantification, pattern guards, etc, etc) are well on the way. However, Haskell 98 will have a special status: the intention is that Haskell compilers will continue to support Haskell 98 (given an appropriate flag) even after later versions of the language have been defined, and so the name `Haskell 98' will refer to a fixed, stable language.

In: Haskell98 report

So, things were simplified, with the goal of producing a simpler standard.