Why were Haskell 98's standard classes made inferior to Haskell 1.3's?
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 (eg 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...
zero
. The Left Zero law applies ( zero >>= k = zero
), so you know what's supposed to happen. 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 (eg 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
). 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.) fmap
much more often than map
, so why not map
and listMap
instead? 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 eg
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.
链接地址: http://www.djcxy.com/p/7506.html上一篇: 功能程序中的终止检查