Wearing the hair shirt�A retrospective on Haskell
Simon Peyton Jones
Microsoft Research, Cambridge
The primoridal soup
FPCA, Sept 1987: initial meeting. A dozen lazy functional programmers, wanting to agree on a common language.
Led to...a succession of face-to-face meetings
April 1990: Haskell 1.0 report released � (editors: Hudak, Wadler)
Timeline
Sept 87: kick off
Apr 90: Haskell 1.0
May 92: Haskell 1.2 (SIGPLAN Notices) (164pp)
Aug 91: Haskell 1.1 (153pp)
May 96: Haskell 1.3. Monadic I/O, separate library report
Apr 97: Haskell 1.4 (213pp)
Feb 99: Haskell 98 (240pp)
Dec 02: Haskell 98 revised (260pp)
The Book!
Haskell 98
Haskell development
Haskell 98
Haskell + extensions
Reflections on the process
Motto: avoid success at all costs
The price of usefulness
Syntax
Syntax
Syntax is not important
Parsing is the easy bit of a compiler
Syntax
Syntax is not important
Syntax is the user interface of a language
Parsing is the easy bit of a compiler
The parser is often the trickiest bit of a compiler
Good ideas from other languages
List comprehensions
head :: [a] -> a
head (x:xs) = x
[(x,y) | x <- xs, y <- ys, x+y < 10]
Separate type signatures
DIY infix operators
f `map` xs
Optional layout
let x = 3
y = 4
in x+y
let { x = 3; y = 4} in x+y
f True true = true
Upper case constructors
Fat vs thin
Expression style
Declaration style
SLPJ’s conclusion
syntactic redundancy is a big win
Tony Hoare’s comment “I fear that Haskell is doomed to succeed”
“Declaration style”
Define a function as a series of independent equations
map f [] = []
map f (x:xs) = f x : map f xs
sign x | x>0 = 1
| x==0 = 0
| x<0 = -1
“Expression style”
Define a function as an expression
map = \f xs -> case xs of
[] -> []
(x:xs) -> map f xs
sign = \x -> if x>0 then 1
else if x==0 then 0
else -1
Example (ICFP02 prog comp)
sp_help item@(Item cur_loc cur_link _) wq vis
| cur_length > limit -- Beyond limit
= sp wq vis
| Just vis_link <- lookupVisited vis cur_loc
= -- Already visited; update the visited
-- map if cur_link is better
if cur_length >= linkLength vis_link then
-- Current link is no better
sp wq vis
else
-- Current link is better
emit vis item ++ sp wq vis'
| otherwise -- Not visited yet
= emit vis item ++ sp wq' vis'
where
vis’ = ...
wq = ...
Guard
Pattern guard
Pattern match
Conditional
Where clause
So much for syntax...
What is important or interesting about Haskell?
What really matters?
Laziness
Type classes
Sexy types
Laziness
But...
So why wear the hair shirt of laziness?
In favour of laziness
Laziness is jolly convenient
sp_help item@(Item cur_loc cur_link _) wq vis
| cur_length > limit -- Beyond limit
= sp wq vis
| Just vis_link <- lookupVisited vis cur_loc
= if cur_length >= linkLength vis_link then
sp wq vis
else
emit vis item ++ sp wq vis'
| otherwise
= emit vis item ++ sp wq' vis'
where
vis’ = ...
wq’ = ...
Used in two cases
Used in one case
Combinator libraries
Recursive values are jolly useful
type Parser a = String -> (a, String)
exp :: Parser Expr
exp = lit “let” <+> decls <+> lit “in” <+> exp
||| exp <+> aexp
||| ...etc...
This is illegal in ML, because of the value restriction
Can only be made legal by eta expansion.
But that breaks the Parser abstraction, �and is extremely gruesome:
exp x = (lit “let” <+> decls <+> lit “in” <+> exp
||| exp <+> aexp
||| ...etc...) x
The big one....
Laziness keeps you honest
Monadic I/O
A value of type (IO t) is an “action” that, when performed, may do some input/output before delivering a result of type t.
eg.
getChar :: IO Char
putChar :: Char -> IO ()
Performing I/O
main :: IO a
Connecting I/O operations
(>>=) :: IO a -> (a -> IO b) -> IO b
return :: a -> IO a
eg.
getChar >>= (\a ->
getChar >>= (\b ->
putChar b >>= (\() ->
return (a,b))))
The do-notation
getChar >>= \a ->
getChar >>= \b ->
putchar b >>= \()->
return (a,b)
do {
a <- getChar;
b <- getChar;
putchar b;
return (a,b)
}
==
Control structures
Values of type (IO t) are first class
So we can define our own “control structures”
forever :: IO () -> IO ()
forever a = do { a; forever a }
repeatN :: Int -> IO () -> IO ()
repeatN 0 a = return ()
repeatN n a = do { a; repeatN (n-1) a }
e.g. repeatN 10 (putChar ‘x’)
Generalising the idea
A monad consists of:
There are lots of useful monads, not only I/O
Monads
Monad combinators (e.g. sequence, fold, etc), and do-notation, work over all monads
Example: a type checker
tcExpr :: Expr -> Tc Type
tcExpr (App fun arg)
= do { fun_ty <- tcExpr fun
; arg_ty <- tcExpr arg
; res_ty <- newTyVar
; unify fun_ty (arg_ty --> res_ty)
; return res_ty }
Tc monad hides all the plumbing:
Robust to changes in plumbing
The IO monad
The IO monad allows controlled introduction of �other effect-ful language features (not just I/O)
What have we achieved?
Purely-functional core
Imperative “skin”
What have we achieved?
e.g. let x=e in ...x....x...
=
....e....e.....
What we have not achieved
e.g. do { ...; x <- f 1; y <- f 2; ...}
?=?
do { ...; y <- f 2; x <- f 1; ...}
Open challenge 1
Open problem: the IO monad has become Haskell’s sin-bin. (Whenever we don’t understand something, we toss it in the IO monad.)
Festering sore:
unsafePerformIO :: IO a -> a
Dangerous, indeed type-unsafe, but occasionally indispensable.
Wanted: finer-grain effect partitioning
e.g. IO {read x, write y} Int
Open challenge 2
Which would you prefer?
do { a <- f x;
b <- g y;
h a b }
h (f x) (g y)
In a commutative monad, it does not matter whether we do (f x) first or (g y).
Commutative monads are very common. (Environment, unique supply, random number generation.) For these, monads over-sequentialise.
Wanted: theory and notation for some cool compromise.
Monad summary
Our biggest mistake
Using the scary term “monad”
rather than
“warm fuzzy thing”
What really matters?
Laziness
Purity and monads
Type classes
Sexy types
SLPJ conclusions
Type classes
Type classes
Initially, just a neat way to get systematic overloading of (==), read, show.
class Eq a where
(==) :: a -> a -> Bool
instance Eq Int where
i1 == i2 = eqInt i1 i2
instance (Eq a) => Eq [a] where
[] == [] = True
(x:xs) == (y:ys) = (x == y) && (xs == ys)
member :: Eq a => a -> [a] -> Bool
member x [] = False
member x (y:ys) | x==y = True
| otherwise = member x ys
Implementing type classes
data Eq a = MkEq (a->a->Bool)
eq (MkEq e) = e
dEqInt :: Eq Int
dEqInt = MkEq eqInt
dEqList :: Eq a -> Eq [a]
dEqList (MkEq e) = MkEq el
where el [] [] = True
el (x:xs) (y:ys) = x `e` y && xs `el` ys
member :: Eq a -> a -> [a] -> Bool
member d x [] = False
member d x (y:ys) | eq d x y = True
| otherwise = member d x ys
Class witnessed by a “dictionary” of methods
Instance declarations create dictionaries
Overloaded functions take extra dictionary parameter(s)
Type classes over time
Incomprehension
Wild enthusiasm
1987
1989
1993
1997
Implementation begins
Despair
Hack, hack, hack
Hey, what’s the big deal?
Type classes are useful
Type classes have proved extraordinarily convenient in practice
class Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
fail :: String -> m a
Note the higher-kinded type variable, m
Quickcheck
ghci> quickCheck propRev� OK: passed 100 tests�� ghci> quickCheck propRevApp� OK: passed 100 tests
Quickcheck (which is just a Haskell 98 library)
propRev :: [Int] -> Bool
propRev xs = reverse (reverse xs) == xs
propRevApp :: [Int] -> [Int] -> Bool
propRevApp xs ys = reverse (xs++ys) ==
reverse ys ++ reverse xs
Quickcheck
quickCheck :: Test a => a -> IO ()
class Test a where
test :: a -> Rand -> Bool
class Arby a where
arby :: Rand -> a
instance (Arby a, Test b) => Test (a->b) where
test f r = test (f (arby r1)) r2
where (r1,r2) = split r
instance Test Bool where
test b r = b
Extensiblity
Type-based dispatch
class Num a where
(+) :: a -> a -> a
negate :: a -> a
fromInteger :: Integer -> a
...
Type-based dispatch
double :: Num a => a -> a�double x = 2*x
means
double :: Num a -> a -> a�double d x = mul d (fromInteger d 2) x
The overloaded value is returned by fromInteger, not passed to it. It is the dictionary (and type) that are passed as argument to fromInteger
class Num a where
(+) :: a -> a -> a
negate :: a -> a
fromInteger :: Integer -> a
...
Type-based dispatch
So the links to intensional polymorphism are much closer than the links to OOP.
The dictionary is like a proxy for the (interesting aspects of) the type argument of a polymorphic function.
f :: forall a. a -> Int
f t (x::t) = ...typecase t...
f :: forall a. C a => a -> Int
f x = ...(call method of C)...
Intensional polymorphism
Haskell
C.f. Crary et al λR (ICFP98), Baars et al (ICFP02)
Cool generalisations
Qualified types
Type classes summary
Sexy types
Sexy types
Haskell has become a laboratory and playground for advanced type hackery
Is sexy good? Yes!
How sexy?
Destination = Fw<:
Open question
What is a good design for user-level type annotation that exposes the power of Fw or Fw<:, but co-exists with type inference?
C.f. Didier & Didier’s MLF work
Modules
Power
Difficulty
Haskell 98
ML functors
Haskell + sexy types
Modules
Power
Haskell 98
ML functors
Haskell + sexy types
Porsche
High power, but poor power/cost ratio
Ford Cortina with alloy wheels
Medium power, with good power/cost
Modules
Encapsulating it all
runST :: (forall s. ST s a) -> a
Stateful computation
Pure result
data ST s a -- Abstract
newRef :: a -> ST s (STRef s a)�read :: STRef s a -> ST s a�write :: STRef s a -> a -> ST s ()
sort :: Ord a => [a] -> [a]
sort xs = runST (do { ..in-place sort.. })
Encapsulating it all
runST :: (forall s. ST s a) -> a
Higher rank type
Monads
Security of encapsulation depends on parametricity
Parametricity depends on there being few polymorphic functions �(e.g.. f:: a->a means f is the identity function or bottom)
And that depends on type classes to make non-parametric operations explicit �(e.g. f :: Ord a => a -> a)
And it also depends on purity (no side effects)
The Haskell committee
Arvind
Lennart Augustsson
Dave Barton
Brian Boutel
Warren Burton
Jon Fairbairn
Joseph Fasel
Andy Gordon
Maria Guzman
Kevin Hammond
Ralf Hinze
Paul Hudak [editor]
John Hughes [editor]
Thomas Johnsson
Mark Jones
Dick Kieburtz
John Launchbury
Erik Meijer
Rishiyur Nikhil
John Peterson
Simon Peyton Jones [editor]
Mike Reeve
Alastair Reid
Colin Runciman
Philip Wadler [editor]
David Wise
Jonathan Young