A Taste of Haskell
Total Page:16
File Type:pdf, Size:1020Kb
Simon Peyton Jones Microsoft Research A tutorial presented at the O‟Reilly Open Source Convention, July 2007 Video of this tutorial (3 hrs) http://research.microsoft.com/~simonpj/papers/haskell-tutorial Haskell is a programming language that is purely functional lazy higher order strongly typed general purpose Functional programming will make you think differently about programming Mainstream languages are all about state Functional programming is all about values Whether or not you drink the Haskell Kool- Aid, you‟ll be a better programmer in whatever language you regularly use 1,000,000 10,000 Practitioners 100 The quick death 1 Geeks 1yr 5yr 10yr 15yr 1,000,000 10,000 Practitioners 100 The slow death 1 Geeks 1yr 5yr 10yr 15yr Threshold of immortality 1,000,000 10,000 Practitioners The complete 100 absence of death 1 Geeks 1yr 5yr 10yr 15yr “Learning Haskell is a great way of training yourself to think functionally “I'm already looking at so you are ready to take full advantage coding problems and my of C# 3.0 when it comes out” mental perspective is now (blog Apr 2007) 1,000,000 shifting back and forth between purely OO and more FP styled solutions” (blog Mar 2007) 10,000 Practitioners 100 The second life? 1 Geeks 1990 1995 2000 2005 2010 xmonad is an X11 tiling window manager written entirely in Haskell Screen Client Client Events (mouse, kbd, client) X11 Window Mouse manager Window placement Keyboard Client Client 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 Code Comments Language metacity >50k C ion3 20k 7k C larswm 6k 1.3k C wmii 6k 1k C dwm 4.2 1.5k 0.2k C xmonad 0.2 0.5k 0.7k Haskell Demo xmonad Configuration Layout data algorithm Events (mouse, kbd, client) X11 FFI State machine Window placement Session state Export A ring of windows list One has the focus module Stack( Stack, insert, swap, ...) where Import things import Graphics.X11( Window ) defined elsewhere type Stack = ... Define Specify type new types insert :: Window -> Stack of insert -- Newly inserted window has focus insert = ... swap :: Stack -> Stack -- Swap focus with next swap = ... Comments Stack should not exploit the fact that it‟s a stack of windows module Stack( Stack, insert, swap, ...) where No import any more type Stack w = ... A stack of values of type w insert :: w -> Stack w -- Newly inserted window has focus insert = ... swap :: Stack w -> Stack w Insert a „w‟ -- Swap focus with next into a stack swap = ... of w‟s a b c d e A list takes one of two forms: A ring of windows • [], the empty list One has the focus • (w:ws), a list whose head is w, and tail is ws 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 The ring above is -- Swap topmost pair represented swap [] = [] [c,d,e,...,a,b] swap (w : []) = w : [] swap (w1 : w2 : ws) = w2 : w1 : ws Functions are w1:w2:ws means w1 : (w2 : ws) defined by pattern matching swap [] = [] swap (w:[]) = w:[] swap (w1:w2:ws) = w2:w1:ws swap [] = [] [a,b,c] swap [w] = [w] means swap (w1:w2:ws) = w2:w1:ws a:b:c:[] swap (w1:w2:ws) = w2:w1:ws Equations are swap ws = ws matched top-to- bottom swap ws = case ws of [] -> [] [w] -> [w] (w1:w2:ws) -> w2:w1:ws case expressions Download: ghc: http://haskell.org/ghc Hugs: http://haskell.org/hugs Interactive: ghci Stack.hs hugs Stack.hs Compiled: ghc –c Stack.hs Demo ghci A ring of windows focusNext :: Stack -> Stack One has the focus 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) 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 : [] A ring of windows One has the focus focusPrev :: Stack -> Stack focusPrev ws = reverse (focusNext (reverse ws)) reverse :: [a] -> [a] Function -- e.g. reverse [1,2,3] = [3,2,1] application reverse [] = [] by mere reverse (x:xs) = reverse xs ++ [x] juxtaposition Function application binds more tightly than anything else: (reverse xs) ++ [x] focusPrev :: Stack -> Stack focusPrev ws = reverse (focusNext (reverse ws)) can also be written focusPrev :: Stack -> Stack focusPrev = reverse . focusNext . reverse reverse focusNext reverse focusPrev Definition of (.) from Prelude (f . g) x = f (g x) Functions as arguments (.) :: (b->c) -> (a->b) -> (a->c) (f . g) x = f (g x) c b a f g f.g It‟s good to write tests as you write code E.g. focusPrev undoes focusNext; swap undoes itself; etc module Stack where ...definitions... -- Write properties in Haskell type TS = Stack Int -- Test at this type prop_focusNP :: TS -> Bool prop_focusNP s = focusNext (focusPrev s) == s prop_swap :: TS -> Bool prop_swap s = swap (swap s) == s Test.QuickCheck is simply a Haskell library (not a “tool”) bash$ ghci Stack.hs Prelude> :m +Test.QuickCheck Prelude Test.QuickCheck> quickCheck prop_swap +++ OK, passed 100 tests Prelude Test.QuickCheck> quickCheck prop_focusNP +++ OK, passed 100 tests ...with a strange- looking type Prelude Test.QuickCheck> :t quickCheck quickCheck :: Testable prop => prop -> IO () Demo QuickCheck A 25-line Haskell script Look for “prop_” tests runHaskell Foo.hs <args> in here runs Foo.hs, passing it <args> bash$ runhaskell QC.hs Stack.hs prop_swap: +++ OK, passed 100 tests prop_focusNP: +++ OK, passed 100 tests No side effects. At all. swap :: Stack w -> Stack w A call to swap returns a new stack; the old one is unaffected. prop_swap s = swap (swap s) == s A variable „s‟ stands for an immutable value, not for a location whose value can change with time. Think spreadsheets! Purity makes the interface explicit swap :: Stack w -> Stack w -- Haskell Takes a stack, and returns a stack; that‟s all void swap( stack s ) /* C */ Takes a stack; may modify it; may modify other persistent state; may do I/O Pure functions are easy to test prop_swap s = swap (swap s) == s 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 Types are everywhere swap :: Stack w -> Stack w Usual static-typing rant omitted... In Haskell, types express high-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 A ring of windows One has the focus type Stack w = [w] -- Focus is head of list enumerate:: Stack w -> [w] -- Enumerate the windows in layout order enumerate s = s Changing focus moves the windows around: confusing! 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 left right 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 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] left right 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 (MkStack ls rs) = reverse ls ++ rs left right data Stack w = MkStk [w] [w] -- left and right resp focusPrev :: Stack w -> Stack w focusPrev (MkStk (l:ls) rs) = MkStk ls (l:rs) focusPrev (MkStk [] rs) = ...???... Nested pattern matching Choices for left=[]: left right • no-op • move focus to end We choose this one left right data Stack w = MkStk [w] [w] -- left and right resp -- Focus is head of „right‟ focusPrev :: Stack w -> Stack w focusPrev (MkStk (l:ls) rs) = MkStk ls (l:rs) focusPrev (MkStk [] rs) = case (reverse rs) of (l:ls) -> MkStk ls [l] Choices: a b c d e f g • no-op • move focus to end left right a b c d e f g Note: I fixed a bug on this slide subsequent to presenting the tutorial left right 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‟ focusPrev :: Stack w -> Stack w focusPrev (MkStk (l:ls) rs) = MkStk ls (l:rs) focusPrev (MkStk [] rs) = case (reverse rs) of (l:ls) -> MkStk ls [l] [] -> MkStk [] [] Pattern matching forces us to confront all the cases Efficiency note: reverse costs O(n), but that only happens once every n calls to focusPrev, so amortised cost is O(1). A new data type has one or more constructors Each constructor has zero or more arguments data Stack w = MkStk [w] [w] Built-in syntactic sugar for lists, but otherwise data Bool = False | True lists are just another data type data Colour = Red | Green | Blue data Maybe a = Nothing | Just a data [a] = [] | a : [a] 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 Maybe a = Nothing | Just a data Stack w = MkStk [w] [w] -- Invariant for (MkStk ls rs) -- rs is empty => ls is empty Data types are used to describe data (obviously) to describe “outcomes” or “control” module Stack( focus, ..