1.18k likes | 1.33k Views
A taste of Haskell . Simon Peyton Jones Microsoft Research. The challenge. “We are in the middle of the most important upheaval in computer science for several decades” “We have to re-visit and re-think pretty much everything”
E N D
A taste of Haskell Simon Peyton Jones Microsoft Research
The challenge • “We are in the middle of the most important upheaval in computer science for several decades” • “We have to re-visit and re-think pretty much everything” • “The end of the von Neumann model” .... “Computing must be re-invented” Burton Smith, 18 July 2009 So we need to think afresh...
Backus 1978 Can programming be liberated from the von Neumann style? John Backus Dec 1924 – Mar 2007
One response • Purely functional programming is a radical and elegant attack on the whole enterprise of software construction • Radical, because it forces us to re-think how programming is done: • Give up side effects? • But side effects are how mainstream languages compute! • If you want to program a parallel computer, then a promising starting point is a language in which side effects are the exception, not the rule
What is Haskell? • Haskell is a programming language that is • purely functional • lazy • higher order • strongly typed • general purpose • parallel by default
Why should I care? • Functional programming will make you think differently about programming • Mainstream languages are all about state • Functional programming is all about values • Learning Haskell will make you a better programmer in whatever language you regularly use
Spectrum Pure (no effects) Any effect C, C++, Java, C#, VB Excel, Haskell X := In1 X := X*X X := X + In2*In2 Commands, control flow Expressions, data flow • Do this, then do that • “X” is the name of a cell that has different values at different times • No notion of sequence • “A2” is the name of a (single) value
xmonad • xmonad is an X11 tiling window manager written entirely in Haskell Client Client Screen Events (mouse, kbd, client) X11 Window manager Mouse Window placement Client Client Keyboard
Why I’m using xmonad • Because it’s • A real program • of manageable size • that illustrates many Haskell programming techniques • is open-source software • is being actively developed • by an active community
Inside xmonad Configuration data Layout algorithm Events (mouse, kbd, client) X11 FFI State machine Window placement Session state (Window model)
The window stack Export list A ring of windows One has the focus Stack.hs module Stack( Stack, insert, swap, ...) where import Graphics.X11( Window ) type Stack = ...Window... focusNext:: Stack -> Stack -- Move focus to next window focusNext = ... swap :: Stack -> Stack -- Swap focus window with next swap = ... Import things defined elsewhere Define new types Specify type of focusNext Comments
The window stack Stack should not exploit the fact that it’s a stack of windows module Stack( Stack, insert, swap, ...) where type Stack w = ... focusNext :: Stack w -> Stack w -- Move focus to next window focusNext = ... swap :: Stack w -> Stack w -- Swap focus with next swap = ... No import any more A stack of values of type w focusNext works fine for any type w
b a The window stack c d e • A list takes one of two forms: • [], the empty list • (w:ws), a list whose head is w, and tail is ws A ring of windows One has the focus The type “[w]” means “list of w” type Stack w = [w] -- Focus is first element of list, -- rest follow clockwise swap :: Stack w -> Stack w -- Swap topmost pair swap [] = [] swap (w : []) = w : [] swap (w1 : w2 : ws) = w2 : w1 : ws The ring above is represented [c,d,e,...,a,b] Functions are defined by pattern matching • w1:w2:ws means w1 : (w2 : ws)
swap [] = [] swap (w:[]) = w:[] swap (w1:w2:ws) = w2:w1:ws Syntactic sugar swap [] = [] swap [w] = [w] swap (w1:w2:ws) = w2:w1:ws [a,b,c] means a:b:c:[] Equations are matched top-to-bottom swap (w1:w2:ws) = w2:w1:ws swap ws = ws swap ws = casewsof [] -> [] [w] -> [w] (w1:w2:ws) -> w2:w1:ws case expressions
Running Haskell • Download: • ghc: http://haskell.org/ghc • Hugs: http://haskell.org/hugs • Interactive: • ghciStack.hs • hugs Stack.hs • Compiled: • ghc –cStack.hs Demo ghci
Rotating the windows A ring of windows One has the focus focusNext :: Stack w -> Stack w focusNext (w:ws) = ws ++ [w] focusNext [] = [] Pattern matching forces us to think of all cases Type says “this function takes two arguments, of type [a], and returns a result of type [a]” (++) :: [a] -> [a] -> [a] -- List append; e.g. [1,2] ++ [4,5] = [1,2,4,5] Definition in Prelude (implicitly imported)
Recursion Recursive call (++) :: [a] -> [a] -> [a] -- List append; e.g. [1,2] ++ [4,5] = [1,2,4,5] [] ++ ys = ys (x:xs) ++ ys = x : (xs ++ ys) Execution model is simple rewriting: [1,2] ++ [4,5] = (1:2:[]) ++ (4:5:[]) = 1 : ((2:[]) ++ (4:5:[])) = 1 : 2 : ([] ++ (4:5:[])) = 1 : 2 : 4 : 5 : []
Rotating backwards A ring of windows One has the focus focusPrev :: Stack w -> Stack w focusPrevws = reverse (focusNext (reverse ws)) Function application by mere juxtaposition reverse :: [a] -> [a] -- e.g. reverse [1,2,3] = [3,2,1] reverse [] = [] reverse (x:xs) = reverse xs ++ [x] Function application binds more tightly than anything else: (reverse xs) ++ [x]
Function composition focusPrev :: Stack w -> Stack w focusPrevws = reverse (focusNext (reverse ws)) can also be written focusPrev :: Stack w -> Stack w focusPrev = reverse . focusNext . reverse focusPrev reverse focusNext reverse Definition of (.) from Prelude (f . g) x = f (g x)
Function composition Functions as arguments (.) :: (b->c) -> (a->b) -> (a->c) (f . g) x = f (g x) f.g c b a f g
Things to notice... No side effects. At all. • A call to swap returns a new stack; the old one is unaffected. • A variable ‘s’ stands for an immutable value, not for a location whose value can change with time. Think spreadsheets! swap :: Stack w -> Stack w prop_swaps = swap (swap s) == s
Things to notice... Purity makes the interface explicit • Takes a stack, and returns a stack; that’s all • Takes a stack; may modify it; may modify other persistent state; may do I/O swap :: Stack w -> Stack w -- Haskell void swap( stack s ) /* C */
Things to notice... Pure functions are easy to test • In an imperative or OO language, you have to • set up the state of the object, and the external state it reads or writes • make the call • inspect the state of the object, and the external state • perhaps copy part of the object or global state, so that you can use it in the postcondition prop_swap :: Stack Int -> Bool prop_swap s = swap (swap s) == s
Things to notice... Types are everywhere • Usual static-typing rant omitted... • In Haskell, types expresshigh-level design, in the same way that UML diagrams do; with the advantage that the type signatures are machine-checked • Types are (almost always) optional: type inference fills them in if you leave them out swap :: Stack w -> Stack w
Improving the design A ring of windows One has the focus • Changing focus moves the windows around: confusing! type Stack w = [w] -- Focus is head of list enumerate:: Stack w -> [w] -- Enumerate the windows in layout order -- They are laid out on the screen in this order enumerate s = s
Improving the design A sequence of windows One has the focus • Want: a fixed layout, still with one window having focus a b c d e f g Data type declaration right left Constructor of the type Represented as MkStk [b,a] [c,d,e,f,g] data Stack w = MkStk [w] [w] -- left and right resp -- Focus is head of ‘right’ list -- Left list is *reversed* -- INVARIANT: if ‘right’ is empty, so is ‘left’
A sequence of windows One has the focus Improving the design • Want: a fixed layout, still with one window having focus a b c d e f g Represented as MkStk [b,a] [c,d,e,f,g] right left data Stack w = MkStk [w] [w] -- left and right resp -- Focus is head of ‘right’ list -- Left list is *reversed* -- INVARIANT: if ‘right’ is empty, so is ‘left’ enumerate :: Stack w -> [w] enumerate (MkStklsrs) = reverse ls ++ rs
Moving focus data Stack w = MkStk [w] [w] -- left and right resp -- Focus is head of ‘right’ focusNext :: Stack w -> Stack w focusNext (MkStkls (r:rs)) = MkStk (r:ls) rs right left Nested pattern matching
Oops.. Warning: Pattern match(es) are non-exhaustive In the case expression: ... Patterns not matched: [] data Stack w = MkStk [w] [w] -- left and right resp -- Focus is head of ‘right’ -- INVARIANT: if ‘right’ is empty, so is ‘left’ focusNext :: Stack w -> Stack w focusNext (MkStkls (r:rs)) = MkStk (r:ls) rs focusNext (MkStkls []) = MkStk [] [] • Pattern matching forces us to confront all the cases • But still not right ls must be empty
ghciStack.hs *Stack> quickCheckprop_focusNP *** Failed! after 1 test and 1 shrink MkStk [] [0] Oops.. data Stack w = MkStk [w] [w] -- left and right resp -- Focus is head of ‘right’ -- INVARIANT: if ‘right’ is empty, so is ‘left’ focusNext :: Stack w -> Stack w focusNext (MkStkls (r:rs)) = MkStk (r:ls) rs focusNext (MkStkls []) = MkStk [] [] focus_NP :: Stack Int -> Bool focus_NP s = focusPrev (focusNext s) == s • Testing properties can rapidly expose bugs Does not obey the INVARIANT; rs might be empty
ghciStack.hs *Stack> quickCheckprop_focusNP +++ OK, passed 100 tests Happy data Stack w = MkStk [w] [w] -- left and right resp -- Focus is head of ‘right’ -- INVARIANT: if ‘right’ is empty, so is ‘left’ focusNext :: Stack w -> Stack w focusNext (MkStkls (r:rs)) = mkStk (r:ls) rs focusNext (MkStkls []) = MkStk [] [] mkStk :: [w] -> [w] -> Stack w -- Smart constructor for MkStk, establishes INVARIANT mkStkls [] = MkStk [] (reverse ls) mkStklsrs = MkStklsrs Efficiency note: reverse costs O(n), but that only happens once every n calls to focusPrev, so amortised cost is O(1).
Data types • A new data type has one or more constructors • Each constructor has zero or more arguments Built-in syntactic sugar for lists, but otherwise lists are just another data type data Stack w = MkStk [w] [w] data Bool = False | True data Colour = Red | Green | Blue data Maybe a = Nothing | Just a data [a] = [] | a : [a]
Data types data Stack w = MkStk [w] [w] data Bool = False | True data Colour = Red | Green | Blue data Maybe a = Nothing | Just a • Constructors are used: • as a function to construct values (“right hand side”) • in patterns to deconstruct values (“left hand side”) isRed :: Colour -> Bool isRed Red = True isRed Green = False isRed Blue = False Patterns Values
Data types data Maybe a = Nothing | Just a data Stack w = MkStk [w] [w] -- Invariant for (MkStklsrs) -- rs is empty => ls is empty • Data types are used • to describe data (obviously) • to describe “outcomes” or “control” module Stack( focus, ... ) where focus :: Stack w -> Maybe w -- Returns the focused window of the stack -- or Nothing if the stack is empty focus (MkStk _ []) = Nothing focus (MkStk _ (w:_)) = Just w A bit like an exception... ...but you can’t forget to catch it No “null-pointer dereference” exceptions module Foo where import Stack foos = ...case (focus s) of Nothing -> ...do this in empty case... Just w -> ...do this when there is a focus...
Data type abstraction module Operations( ... ) where import Stack( Stack, focusNext ) f :: Stack w -> Stack w f (MkStk as bs) = ... OK: Stack is imported NOT OK: MkStk is not imported module Stack( Stack, focusNext, focusPrev, ... ) where data Stack w = MkStk [w] [w] focusNext :: Stack w -> Stack w focusNext (MkStklsrs) = ... Stack is exported, but not its constructors; so its representation is hidden
Haskell’s module system module X where import P import Q h = (P.f, Q.f, g) • Module system is merely a name-spacecontrol mechanism • Compilertypically does lots of cross-moduleinlining • Modules can begrouped intopackages module P(f,g) where import Z(f) g = ... module Q(f) where f = ... module Z where f = ...
Where is the I/O in xmonad? • All this pure stuff is very well, but sooner or later we have to • talk to X11, whose interface is not at all pure • do input/output (other programs) A functional program defines a pure function, with no side effects The whole point of running a program is to have some side effect Tension
Where is the I/O in xmonad? • All this pure stuff is very well, but sooner or later we have to • talk to X11, whose interface is not at all pure • do input/output (other programs) Configuration data Layout algorithm Events (mouse, kbd, client) FFI State machine X11 Window placement Session state
Doing I/O putStr :: String -> () -- Print a string on the console • Idea: • BUT: nowmight do arbitrary stateful things • And what does this do? • What order are the things printed? • Are they printed at all? swap :: Stack w -> Stack w [putStr “yes”, putStr “no”] Order of evaluation! Laziness!
The main idea A value of type (IO t) is an “action” that, when performed, may do some input/output before delivering a result of type t. putStr :: String -> IO () -- Print a string on the console • “Actions” sometimes called “computations” • An action is a first class value • Evaluating an action has no effect; performingthe action has an effect
A helpful picture A value of type (IO t) is an “action” that, when performed, may do some input/output before delivering a result of type t. type IO a = World -> (a, World) -- An approximation result :: a IO a World out World in
Simple I/O () String String getLine putStr getLine :: IO String putStr :: String -> IO () main :: IO () main = putStr “Hello world” Main program is an action of type IO ()
Connecting actions up () String getLine putStr Goal: read a line and then write it back out
Connecting actions up echo :: IO () echo = do{ l <- getLine; putStrl } () String getLine putStr echo We have connected two actions to make a new, bigger action.