Wearing The Hair Shirt
Wearing The Hair Shirt
Wearing the
the hair
hair shirt
shirt
A
A retrospective
retrospective on
on Haskell
Haskell
Simon Peyton Jones
Microsoft Research, Cambridge
Haskell is 15 years old
(born FPCA 87)
Haskell is 15 years old
(born FPCA’87)
Haskell
Haskell + extensions
development • Dynamic, exciting
• Unstable,
undocumented,
implementations vary...
Reflections on the process
The idea of having a fixed standard
(Haskell 98) in parallel with an evolving
language, has worked really well
Formal semantics only for fragments (but
see [Faxen2002])
A smallish, rather pointy-headed user-
base makes Haskell nimble. Haskell has
evolved rapidly and continues to do so.
Motto: avoid success at all costs
The price of usefulness
Libraries increasingly important:
– 1996: Separate libraries Report
– 2001: Hierarchical library naming structure,
increasingly populated
Foreign-function interface increasingly
important
– 1993 onwards: a variety of experiments
– 2001: successful effort to standardise a FFI
across implementations
Any language large enough to be useful is
dauntingly complex
Reflections on process
Self-appointed committee initially,
but increasingly open process: there
is now no Haskell committee
Language development by user
suggestion + implementers
Gives too much power to
implementers?
Syntax
Good ideas from other languages
List comprehensions
let { x = 3; y = 4} in x+y
Syntactic redundancy
Seductive idea: provide just one way
of doing any particular thing
Haskell’s choice: provide multiple
ways, and let the programmer decide
Main example: “declaration style” vs
“expression style”
“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
SLPJ’s conclusion
syntactic redundancy is a big win
Tony Hoare’s comment “I fear that Haskell is doomed to succeed”
Example (ICFP02 prog comp)
Pattern
match
What is important or
interesting about
Haskell?
What really matters?
Laziness
Type classes
Sexy types
Laziness
John Hughes’s famous paper “Why
functional programming matters”
– Modular programming needs powerful
glue
– Lazy evaluation enables new forms of
modularity; in particular, separating
generation from selection.
– Non-strict semantics means that
unrestricted beta substitution is OK.
But...
Laziness makes it much, much harder to
reason about performance, especially
space. Tricky uses of seq for effect
seq :: a -> b -> b
Laziness has a real implementation cost
Laziness can be added to a strict language
(although not as easily as you might think)
And it’s not so bad only having β V instead
of β
So why wear the hair shirt of laziness?
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'
Used in
two cases | otherwise
= emit vis item ++ sp wq' vis'
where
vis’ = ...
wq’ = ...
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...
exp x = (lit “let” <+> decls <+> lit “in” <+> exp
||| exp <+> aexp
||| ...etc...) x
The big
one....
Laziness keeps you honest
Every call-by-value language has given into the
siren call of side effects
But in Haskell
(print “yes”) + (print “no”)
just does not make sense. Even worse is
[print “yes”, print “no”]
So effects (I/O, references, exceptions) are
just not an option.
Result: prolonged embarrassment. Stream-
based I/O, continuation I/O...
but NO DEALS WIH THE DEVIL
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
A program is a single I/O action
Running the program performs the action
Can’t do I/O from pure code.
Result: clean separation of pure code from
imperative code
Connecting I/O operations
eg.
getChar >>= (\a ->
getChar >>= (\b ->
putChar b >>= (\() ->
return (a,b))))
The do-notation
do {
getChar >>= \a -> a <- getChar;
==
getChar >>= \b -> b <- getChar;
putchar b >>= \()-> putchar b;
return (a,b) return (a,b)
}
Purely-
functional core
What have we achieved?
...without ruining either
All laws of pure functional programming
remain unconditionally true, even of actions
e.g. let x=e in ...x....x...
=
....e....e.....
What we have not achieved
Festering sore:
unsafePerformIO :: IO a -> a
Dangerous, indeed type-unsafe, but occasionally
indispensable.
do { a <- f x;
b <- g y; h (f x) (g y)
h a b }
Laziness
Hey, what’s
Wild enthusiasm the big
deal?
Despair Hack,
Incomprehension hack,
hack
Open question
What is a good design for user-level
type annotation that exposes the
w w
power of F or F <: , but co-exists
with type inference?
C.f. Didier & Didier’s MLF work
ML
Modules functor
s
Difficulty
Haskell +
Haskel sexy types
l 98
Power
ML
Modules
Porsche
High power, but poor power/cost ratio
functor
s
• Separate module language
• First class modules problematic
• Big step in compiler complexity
• Full power seldom needed
Haskell +
Haskel sexy types
l 98
Ford Power
Cortina with alloy wheels
Medium power, with good power/cost
• Module parameterisation too weak
• No language support for module signatures
Modules
Haskell has many features that overlap with what
ML-style modules offer:
– type classes
– first class universals and existentials
Does Haskell need functors anyway? No: one
seldom needs to instantiate the same functor at
different arguments
But Haskell lacks a way to distribute “open”
libraries, where the client provides some base
modules; need module signatures and type-safe
linking (e.g. PLT,Knit?). π not λ !
Wanted: a design with better power, but good
power/weight.
Encapsulating it all
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 ()
Stateful
computation Pure result
Security of Monads
encapsulation
depends on
parametricity And that depends on type
classes to make non-parametric
operations explicit
(e.g. f :: Ord a => a -> a)
Parametricity depends on there
being few polymorphic functions
And it also depends
(e.g.. f:: a->a means f is the on purity (no side
identity function or bottom) effects)
Shirts off to Wadler
Type classes “Making ad hoc polymorphism
less ad hoc” [POPL89]