Haskell a lazy, purely func onal language
COS 326 David Walker Princeton University
slides copyright 2013-2015 David Walker and Andrew W. Appel permission granted to reuse these slides for non-commercial educa onal purposes
Haskell Another cool, typed, func onal programming language • Like OCaml in that: – it is a func onal language with parametric polymorphism • Unlike OCaml in that it is: – pure: func ons with type a -> b have no effect! – lazy: f e does not evaluate e right away; passes e to f – has a weak module system; uses type classes – has a very sophis cated type system with higher kinds – has several cool extensions for concurrent and parallel programming including transac onal memory Glasgow in the 1990s ghc stands for “Glasgow Haskell Compiler”
Phil Wadler Simon Peyton Jones (now at Microso Research, (now at U. Edinburgh) Cambridge, England) Creators of Haskell language
Edinburgh in the 1970s
Robin Milner, Luca Cardelli, Luis Damas, Mads To e . . . created ML language
Why are we ge ng all our func onal programming languages from Scotland? HASKELL BASICS Haskell Defini ons • Mostly like OCaml, with a few small syntac c differences – parameters are immutable by default – let declara ons introduce new func ons and value
foo z = let triple x = x*3 in triple z
– equivalently, we might use a "where" defini on:
foo z = triple z where triple x = x*3
Haskell Indenta on • Haskell, like Python, but unlike Java, OCaml or math wri en in a notebook, has seman cally meaningful indenta on • Wrong:
must indent copies k n = func on if n == 0 then [] body else k : copies k (n-1)
zap z = zap z = let x = z let x = z indent z + z y = y = z + z z + z in x + y in x + y indent y = ... Haskell Indenta on • Haskell, like Python, but unlike Java, OCaml or math wri en in a notebook, has seman cally meaningful indenta on • Right: beginning of x defines indenta on level
copies k n = zap z = zap z = if n == 0 then let let x = z [] x = z y = z + z else y = z + z in k : copies k (n-1) in x + y x + y
Haskell Types • We have the op on of declaring the type of a defini on prior to the defini on itself just to be annoying, Haskell uses "::" for "has type" and uses ":" for "cons" – the opposite zap :: Int -> Int of ML zap z = let x = z y = z + z in x + y
Tuples • Haskell uses tuples like ML – constructed by enclosing a sequence of values in parens:
(‘b’, 4) :: (Char, Integer)
– deconstructed (used) via pa ern matching:
easytoo :: (Integer, Integer, Integer) -> Integer easytoo (x, y, z) = x + y * z Lists • Lists are very similar to ML lists but the type is wri en [t] instead of "t list"
[1, 2, 3] :: [Integer]
[‘a’, ‘b’, ‘c’] :: [Char]
• [ ] is the empty list (called nil) • 1:2:[] is a list with 2 elements • String is a synonym for [Char] • We can build lists of lists:
[ [1, 2], [3], [8, 9, 10] ] :: [ [ Integer ] ]
Func ons over lists
-- Sum the elements of a list
listSum :: [ Integer ] -> Integer
listSum [ ] = 0 listSum (x:xs) = x + listSum xs
common to write func ons using mul ple clauses, where each clause matches on a different case Func ons deconstruc ng lists
-- Sum the elements of a list
listSum :: [ Integer ] -> Integer
listSum [ ] = 0 listSum (x:xs) = x + listSum xs lower case le er = any type at all (a type variable) upper case le er = a concrete type length :: [a] -> Int
length [ ] = 0 length (x:xs) = 1 + length xs Func ons deconstruc ng lists
-- Sum the elements of a list
listSum :: [ Integer ] -> Integer
listSum [ ] = 0 listSum (x:xs) = x + listSum xs
cat :: [a] -> [a] -> [a] length :: [a] -> Int cat [ ] xs2 = xs2
cat (x:xs) xs2 = x:(cat xs xs2) length [ ] = 0 length (x:xs) = 1 + length xs Func ons deconstruc ng lists
-- Sum the elements of a list
listSum :: [ Integer ] -> Integer
listSum [ ] = 0 listSum (x:xs) = x + listSum xs
cat :: [a] -> [a] -> [a] length :: [a] -> Int cat [ ] xs2 = xs2
cat (x:xs) xs2 = x:(cat xs xs2) length [ ] = 0 length (x:xs) = 1 + length xs (++) :: [a] -> [a] -> [a]
(++) [ ] xs2 = xs2 (++) (x:xs) xs2 = x:(xs ++ xs2) PURITY & SUBSTITUTION OF EQUALS FOR EQUALS Subs tu on of Equals for Equals • A key law about Haskell programs:
let x =
• For example:
let x = 4 `div` 2 in (4 `div` 2) + 5 + (4 `div` 2) x + 5 + x =
= 9
*Note: not necessarily the same run me; (4 `div` 2) will be evaluated twice instead of once. Subs tu on of Equals for Equals • We'd also like to use func onal abstrac on without penalty halve :: Int -> Int halve n = n `div` 2 • And instead of telling clients about all implementa on details, simply expose key laws:
Lemma 1: for all n, if even n then (halve n + halve n) = n
• Now we can reason locally within the client: let x = halve 4 in x + y + x = (halve 4) + y + (halve 4) (subs tu on) = (halve 4) + (halve 4) + y (arithme c) = 4 + y (Lemma 1)
Computa onal Effects • What happens when we add mutable data structures? • Consider this OCaml program: let x = ref 0 foo : int -> int
let foo (y:int) : int = x := !x + 1; arg + !x;
• We lose a lot of reasoning power!
let y = foo 3 in foo 3 + foo 3 y + y ≠ Computa onal Effects • What happens when we add mutable data structures? • Consider this OCaml program: let x = ref 0 foo : int -> int
let foo (y:int) : int = x := !x + 1; arg + !x;
• We lose a lot of reasoning power!
let y = foo 3 in foo 3 + foo 3 y + y ≠
8 9 Computa onal Effects • What happens when we add mutable data structures? • Consider this OCaml program: foo : int -> int
let foo (y:int) : int = print_int y; arg + !x;
• We lose a lot of reasoning power!
let y = foo 3 in foo 3 + foo 3 y + y ≠
6 prin ng "3" 6 prin ng "33" Computa onal Effects • A func on has an effect if its behavior cannot be specified exclusively as a rela on between its input and its output – I/O is an effect – An impera ve update of a data structure is an effect • When func ons can no longer be described exclusively in terms of the rela onship between arguments and results – many, many fewer equa onal laws hold – In general, in OCaml,
let x =
– In general, in Haskell, let x =
– In general, in Haskell, let x =
p a b c = a*a + b*b = c*c triples = do a <- [1..25] b <- [a..25] c <- [b..25] guard (p a b c) return (a,b,c)
Source: Greg Bacon, h p://gbacon.blogspot.com/2009/07/programmable-semicolon-explained.html
List comprehensions Example: Find all (a,b,c) such that 1 ≤ a ≤ b ≤ c ≤ 25 and a2+b2=c2
p a b c = a*a + b*b = c*c
triples = do It looks like some sort of “iterator” feature; a <- [1..25] but Haskell doesn’t have built-in “iterators.” b <- [a..25] It's got something more general! Iterators arise as a natural consequence of: c <- [b..25] pure func onal programming guard (p a b c) + lazy evalua on + lists return (a,b,c) + a nice nota on for monadic computa ons
Now, let’s “unpack” this to see how that works.
Source: Greg Bacon, h p://gbacon.blogspot.com/2009/07/ programmable-semicolon-explained.html
List comprehensions Example: Find all (a,b,c) such that 1 ≤ a ≤ b ≤ c ≤ 25 and a2+b2=c2
triples = do { a <- [1..25] ;
b <- [a..25] ; An alternate syntax using semi-colons. c <- [b..25] ; The semi-colon opera on guard (p a b c) ; actually does a lot of work. It does more than just return (a,b,c) "move on to the next statement". } It "composes" the func ons defined by each statement.
Source: Greg Bacon, h p://gbacon.blogspot.com/2009/07/ programmable-semicolon-explained.html
List comprehensions Example: Find all (a,b,c) such that 1 ≤ a ≤ b ≤ c ≤ 25 and a2+b2=c2 triples = do { a <- [1..25] ;
b <- [a..25] ; This is just the “range” operator for lists; c <- [b..25] ; [1..5] is just [1,2,3,4,5]. Easy to construct guard (p a b c) ; this with “let rec” in ML, or as a recursive func on in Haskell. No magic here. return (a,b,c) }
List comprehensions “a <- e ; …” is just a nota onal abbrevia on for the monadic bind operator that you’ve seen before. Haskell makes it easy to introduce nota onal abbrevia ons That’s λ from lambda-calculus; in ML you’d write “fun” triples = do { triples = a <- [1..25] ; [1..25] >>= \a -> b <- [a..25] ; [a..25] >>= \b -> c <- [b..25] ; [b..25] >>= \c -> guard (p a b c) >> guard (p a b c) ; return (a,b,c) return (a,b,c) }
List comprehensions “a <- e ; …” is just a nota onal abbrevia on for the monadic bind operator that you’ve seen before. Haskell makes it easy to introduce nota onal abbrevia ons That’s λ from lambda-calculus; in ML you’d write “fun” triples = do { triples = a <- [1..25] ; [1..25] >>= \a -> b <- [a..25] ; [a..25] >>= \b -> c <- [b..25] ; [b..25] >>= \c -> guard (p a b c) >> guard (p a b c) ; return (a,b,c) return (a,b,c) }
do {a <- e1; e2} == e1 >>= (\a -> e2) == e1 >>= (fun a -> e2) List Monad You’ve seen monads before, but let me remind you:
(* in Ocaml nota on, not Haskell! *) One way to think of a monad 'a M: It is a special sort of "container" for 'a. module type MONAD = sig return x : puts x into the container
type ‘a M c >>= f : extracts items from c; val return : ‘a -> ‘a M then pushes them through f, which generates new container val (>>=) : ‘a M -> (‘a -> ‘b M) -> ‘b M Bind (c >> f) allows you to compute with end the items inside the container pronounced “bind” List Monad You’ve seen monads before, but let me remind you:
(* in Ocaml nota on, not Haskell! *) One way to think of a monad 'a M: It is a special sort of "container" for 'a. module type MONAD = sig return x : puts x into the container
type ‘a M c >>= f : extracts items from c; val return : ‘a -> ‘a M then pushes them through f, which generates new container val (>>=) : ‘a M -> (‘a -> ‘b M) -> ‘b M Bind (c >> f) allows you to compute with end the items inside the container
pronounced “bind” When we did error processing, the container was an op on type: 'a op on. At most one thing was in the container at a me. List Monad Here, the monad we want is the List monad with concatMap
(* in Ocaml nota on, not Haskell! *) module type MONAD = sig
type ‘a M
val return : ‘a -> ‘a M
val (>>=) : ‘a M -> (‘a -> ‘b M) -> ‘b M end
One more thing: >> is just a “bind” that throws away its argument.
let (>>) (m: ‘a M) (f: ‘b M) : ‘b M = m >>= fun _ -> f List Monad Here, the monad we want is the List monad with concatMap
(* in Ocaml nota on, not Haskell! *) (* in Ocaml nota on, not Haskell! *) module type MONAD = sig module ListMonad : MONAD
type ‘a M type ‘a M = ‘a list
val return : ‘a -> ‘a M let return (x: ‘a) = [x]
val (>>=) : ‘a M -> (‘a -> ‘b M) -> ‘b M let (>>=) (as: ‘a list) (f: ‘a -> ‘b list) : ‘b list = reduce append nil (map f as) end end List Monad Here, the monad we want is the List monad with concatMap
(* in Ocaml nota on, not Haskell! *) (* in Ocaml nota on, not Haskell! *) module type MONAD = sig module ListMonad : MONAD
type ‘a M type ‘a M = ‘a list
val return : ‘a -> ‘a M let return (x: ‘a) = [x]
val (>>=) : ‘a M -> (‘a -> ‘b M) -> ‘b M let (>>=) (as: ‘a list) (f: ‘a -> ‘b list) : ‘b list = reduce append nil (map f as) end end
let double a = [a;a]
[1;2;3] >>= double == reduce append nil [ [1;1]; [2;2]; [3;3] ] == [1;1;2;2;3;3] Example
[1,2,3] >>= \a -> let f a = return (a,a) in return (a,a) = reduce append nil (map f [1,2,3])
return x = [x]
(>>=) xs f = reduce append nil (map f xs) Example
[1,2,3] >>= \a -> let f a = return (a,a) in return (a,a) = reduce append nil (map f [1,2,3])
let f a = return (a,a) = in reduce append nil [f 1, f 2, f 3]
return x = [x]
(>>=) xs f = reduce append nil (map f xs) Example
[1,2,3] >>= \a -> let f a = return (a,a) in return (a,a) = reduce append nil (map f [1,2,3])
let f a = return (a,a) = in reduce append nil [f 1, f 2, f 3]
reduce append nil [return (1,1), = return (2,2), return (3,3) ]
return x = [x]
(>>=) xs f = reduce append nil (map f xs) Example
[1,2,3] >>= \a -> let f a = return (a,a) in return (a,a) = reduce append nil (map f [1,2,3])
let f a = return (a,a) = in reduce append nil [f 1, f 2, f 3]
reduce append nil [return (1,1), = return (2,2), return (3,3) ]
reduce append nil [ [(1,1)], = [(2,2)], [(3,3)] ] return x = [x]
(>>=) xs f = reduce append nil (map f xs) Example
[1,2,3] >>= \a -> let f a = return (a,a) in return (a,a) = reduce append nil (map f [1,2,3])
let f a = return (a,a) = in reduce append nil [f 1, f 2, f 3]
reduce append nil [return (1,1), = return (2,2), return (3,3) ]
reduce append nil [ [(1,1)], = [(2,2)], [(3,3)] ] return x = [x] [ (1,1), (2,2), (3,3) ] = (>>=) xs f = reduce append nil (map f xs) Example foo = [1..3] >>= \a -> let f a = [a..4] >>= \b -> return (a,b) [a..4] >>= \b -> = in reduce app nil (map f [1,2,3]) return (a,b)
= let f a = [a..4] >>= \b -> return (a,b) in reduce app nil [f 1, f 2, f 3]
reduce app nil = [[1,2,3,4] >>= \b -> return (1,b), [2,3,4] >>= \b -> return (2,b), [3,4] >>= \b -> return (3,b) ] return (x: ‘a) = [x]
(>>=) (al: [a]) (f: a -> [b]) : [b] = reduce app nil (map f al) Example foo = [1..3] >>= \a -> let f a = [a..4] >>= \b -> return (a,b) [a..4] >>= \b -> = in reduce app nil (map f [1,2,3]) return (a,b)
= let f a = [a..4] >>= \b -> return (a,b) in reduce app nil [f 1, f 2, f 3]
reduce app nil = [[1,2,3,4] >>= \b -> return (1,b), [2,3,4] >>= \b -> return (2,b), [3,4] >>= \b -> return (3,b) ] return (x: ‘a) = [x]
(>>=) (al: [a]) (f: a -> [b]) : [b] = reduce app nil (map f al) Example reduce app nil foo = [[1,2,3,4] >>= \b -> return (1,b), [1..3] >>= \a -> [2,3,4] >>= \b -> return (2,b), [a..4] >>= \b -> = [3,4] >>= \b -> return (3,b) return (a,b) ]
reduce app nil [reduce app nil [return(1,1),return(1,2),return(1,3),return(1,4)], = reduce app nil [return(2,2),return(2,3),return(2,4)], reduce app nil [return(3,3),return(3,4)] ] reduce app nil [reduce app nil [[(1,1)],[(1,2)],[(1,3)],[(1,4)]], reduce app nil [[(2,2)],[(2,3)],[(2,4)]], = reduce app nil [[(3,3)],[(3,4)]] ] return (x: ‘a) = [x]
(>>=) (al: [a]) (f: a -> [b]) : [b] = reduce app nil (map f al) Example reduce app nil [1..3] >>= \a -> [reduce app nil [[(1,1)],[(1,2)],[(1,3)],[(1,4)]], [a..4] >>= \b -> = reduce app nil [[(2,2)],[(2,3)],[(2,4)]], return (a,b) reduce app nil [[(3,3)],[(3,4)]] ]
reduce app nil [[(1,1),(1,2),(1,3),(1,4)], = [(2,2),(2,3),(2,4)]], [(3,3),(3,4)]] ]
= [(1,1),(1,2),(1,3),(1,4),(2,2),(2,3),(2,4),(3,3),(3,4)] return (x: ‘a) = [x]
(>>=) (al: [a]) (f: a -> [b]) : [b] = reduce app nil (map f al) Is it really an iterator?
do a <- [1..3] [(1,1),(1,2),(1,3),(1,4),(2,2),(2,3),(2,4),(3,3),(3,4)] b <- [a..4] = return (a,b)
It doesn’t seem like “in the spirit of an iterator” to return the whole list; it should return the pairs (1,1); (1,2); … one at a me, on demand. That’s what we mean by an iterator.
Aha! Every expression in Haskell is lazy. Everything is on-demand. This really is an iterator! Returning to the “Pythagorean triples” … do [1..25] >>= \a -> [1..25] >>= \a -> a <- [1..25] [a..25] >>= \b -> [a..25] >>= \b -> b <- [a..25] [b..25] >>= \c -> [b..25] >>= \c -> c <- [b..25] guard (p a b c) >> guard (p a b c) >>= \ _ -> guard (p a b c) return (a,b,c) return (a,b,c) return (a,b,c)
guard :: (MonadPlus m) => Bool -> m () guard True = return () guard False = mzero “return ()” in the List monad is [ () ]
The “zero” for the List monad is [ ] Example of “guard” do [1..5] >>= \a -> a <- [1..5] = guard (isprime a) >>= \ _ -> guard (isprime a) return (a+1) return (a+1)
f a = guard (isprime a) >>= \ _ -> return (a+1) = reduce app nil (map f [1,2,3,4,5])
reduce app nil [ = guard (isprime 1) >> \_ return (1+1), guard (isprime 2) >> \_ return (2+1), guard (isprime 3) >> \_ return (3+1), guard True = [ () ] guard (isprime 4) >> \_ return (4+1), guard False = [ ] guard (isprime 5) >> \_ return (5+1) ] Example of “guard” do reduce app nil [ a <- [1..5] guard False >> \_ return (1+1), guard (isprime a) = guard True >> \_ return (2+1), return (a+1) guard True >> \_ return (3+1), guard False >> \_ return (4+1), guard True >> \_ return (5+1) ] reduce app nil [ fold app nil (map (\_ return (1+1)) []), fold app nil (map (\_ return (2+1)) [ () ]), guard True = [ () ] = fold app nil (map (\_ return (3+1)) [ () ]), guard False = [ ] fold app nil (map (\_ return (4+1)) []), fold app nil (map (\_ return (5+1)) [ () ]) ] = reduce app nil [ [], [2+1], [3+1], [] [5+1] ] = [2+1, 3+1, 5+1] = [2+1, 3+1, 5+1] = [3,4,6] Returning to the “Pythagorean triples” … do a <- [1..25] b <- [a..25] This is called a “list comprehension” c <- [b..25] guard (p a b c) return (a,b,c) In monadic computa on, in any collec on monad (not just List), guard serves as a filter for comprehensions
There was no magic here (except for lazy func onal programming). Monads, “bind”, “guard”, the List monad, are all defined as user-level func ons in the Haskell language itself. HASKELL EFFECTS INPUT AND OUTPUT I/O in Haskell • Haskell has a special kind of value called an ac on that describes an effect on the world
• Pure ac ons, which just do something and have no interes ng result are values of type IO ()
• Eg: putStr takes a string and yields an ac on describing the act of displaying this string on stdout
-- writes string to stdout putStr :: String -> IO ()
-- writes string to stdout followed by newline putStrLn :: String -> IO () I/O in Haskell • When do ac ons actually happen?
• Ac ons happen under two circumstances:* 1. the ac on defined by main happens when your program is executed (ie: when ghc compiles your program and then you run it)
2. the ac on defined by any expression happens when that expression is wri en at the ghci interpreter prompt (this is pre y similar to the last one – the expression is essen al an en re program)
* there is one other circumstance: Haskell contains some special, unsafe func ons that will perform I/O, most notably System.IO.Unsafe.unsafePerformIO so I lied when earlier when I said the equa on holds in general for Haskell programs ... I/O in Haskell hello.hs: main :: IO () main = putStrLn “Hello world”
in my shell: $ ghc hello.hs [1 of 1] Compiling Main ( hello.hs, hello.o ) Linking hello.exe ...
$ ./hello.exe hello world!
bar.hs: bar :: Int -> IO () bar n = putStrLn (show n ++ “ is a super number”)
main :: IO () main = bar 6
in my shell: $ ghcii.sh GHCi, version 7.0.3: h p://www.haskell.org/ghc/ :? for help Loading package ghc-prim ... linking ... done. Loading package integer-gmp ... linking ... done. Loading package base ... linking ... done. Loading package ffi-1.0 ... linking ... done. Prelude> :l bar [1 of 1] Compiling Main ( bar.hs, interpreted ) Ok, modules loaded: Main. *Main> bar 17 17 is a super number *Main> main 6 is a super number *Main> Ac ons • Ac ons are descrip ons of effects on the world. Simply wri ng an ac on does not, by itself cause anything to happen
bar.hs: hellos :: [IO ()] hellos = [putStrLn “Hi”, putStrLn “Hey”, putStrLn “Top of the morning to you”]
main = hellos !! 2
� !! � in my shell: Prelude> :l hellos gives �th element ... of list � *Main> main Top of the morning to you *Main> Ac ons • Ac ons are just like any other value -- we can store them, pass them to func ons, rearrange them, etc: sequence_ :: [IO ()] -> IO ()
baz.hs: hellos :: [IO ()] hellos = [putStrLn “Hi”, putStrLn “Hey”, putStrLn “Top of the morning to you”]
main = sequence_ (reverse hellos)
in my shell: Prelude> :l hellos ... *Main> main Top of the morning to you Hey HI Combining Ac ons • The infix operator >> takes two ac ons a and b and yields an ac on that describes the effect of execu ng a then execu ng b a erward
howdy :: IO () howdy = putStr “how” >> putStrLn “dy”
• To combine many ac ons, use do nota on:
bonjour :: IO () bonjour = do putStr “Bonjour!” putStr “ ” putStrLn “Comment ca va?” Quick Aside: Back to SEQEQ* • Do we s ll have it? Yes!
let a = PutStrLn "hello" in do do a = PutStrLn "hello" a PutStrLn "hello"
an ac on made up of doing a an ac on made up of doing and then doing a again where the PutStrLn "hello" ac on and a is the PutStrLn "hello" ac on then doing the PutStrLn "hello" ac on again
* SEQEQ = subs tu on of equals for equals Input Ac ons • Some ac ons have an effect and yield a result:
-- get a line of input getLine :: IO String
-- get all of standard input un l end-of-file encountered getContents :: IO String
-- get command line argument list getArgs :: IO [String]
• What can we do with these kinds of ac ons? – we can extract the value and sequence the effect with another: Input Ac ons • Some ac ons have an effect and yield a result:
-- get a line of input getLine :: IO String
-- get all of standard input un l end-of-file encountered getContents :: IO String
-- get command line argument list getArgs :: IO [String]
• What can we do with these kinds of ac ons? – we can extract the value and sequence the effect with another:
do s <- getLine putStrLn s Input Ac ons • Some ac ons have an effect and yield a result:
-- get a line of input getLine :: IO String
-- get all of standard input un l end-of-file encountered getContents :: IO String
-- get command line argument list getArgs :: IO [String]
• What can we do with these kinds of ac ons? – we can extract the value and sequence the effect with another:
do s <- getLine putStrLn s s has type string getLine has type IO string Input Ac ons • Some ac ons have an effect and yield a result:
-- get a line of input getLine :: IO String
-- get all of standard input un l end-of-file encountered Think of type IO String getContents :: IO String as a box containing a computa on that will -- get command line argument list do some work and then The "do" packages up the getArgs :: IO [String] produce a string. getLine getLine and the putStrLn is such a box. the <- gets s ac ons up in to a bigger • s the String out of the box composite box What can we do with these kinds of ac ons? – we can extract the value and sequence the effect with another:
do s <- getLine putStrLn s Input Ac ons • A whole program:
main :: IO () main = do putStrLn “What’s your name?” s <- getLine putStr “Hey, “ putStr s putStrLn “, cool name!” contains readFile import System.IO import modules import System.Environment contains getArgs, getProgName processArgs :: [String] -> String processArgs [a] = a processArgs _ = ""
echo :: String -> IO () echo "" = putStrLn "Bad Args!" echo fileName = do s <- readFile fileName putStrLn "Here it is:" putStrLn "***********" putStr s putStrLn "\n***********" <- nota on:
main :: IO () RHS has type IO T main = do LHS has type T args <- getArgs let fileName = processArgs args let nota on: echo fileName RHS has type T LHS has type T SEQEQ (Again!) • Recall: s1 ++ s2 concatenates String s1 with String s2 • A valid reasoning step:
let s = "hello" in do do putStrLn (s ++ s) putStrLn ("hello" ++ "hello") =
SEQEQ (Again!) • Recall: s1 ++ s2 concatenates String s1 with String s2 • A valid reasoning step:
let s = "hello" in do do putStrLn (s ++ s) putStrLn ("hello" ++ "hello") = • A valid reasoning step:
do let s = "hello" do putStrLn (s ++ s) = putStrLn ("hello" ++ "hello")
SEQEQ (Again!) • Recall: s1 ++ s2 concatenates String s1 with String s2 • A valid reasoning step:
let s = "hello" in do do putStrLn (s ++ s) putStrLn ("hello" ++ "hello") = • A valid reasoning step:
do let s = "hello" do putStrLn (s ++ s) = putStrLn ("hello" ++ "hello") • Wait, what about this: wrong type: getLine :: IO String do s <- getLine do putStrLn (s ++ s) ≠ putStrLn (getLine ++ getLine) SEQEQ (Again!) • Invalid reasoning step?
let s = getLine in ? do do putStrLn (s ++ s) = putStrLn (getLine ++ getLine) SEQEQ (Again!) • Invalid reasoning step?
let s = getLine in ? do do putStrLn (s ++ s) = putStrLn (getLine ++ getLine)
wrong type: wrong type: s :: IO String getLine :: IO String SEQEQ (Again!) • Invalid reasoning step?
let s = getLine in ? do do putStrLn (s ++ s) = putStrLn (getLine ++ getLine)
wrong type: wrong type: s :: IO String getLine :: IO String
• The Haskell type system shows x <- e is different from let x = e – x has a different type in each case – let x = e enables subs tu on of e for x in what follows – x <- e does not enable subs tu on -- a emp ng subs tu on leaves you with code that won't even type check because x and e have different types (type T vs. type IO T) Magic? The List monad was not “magic.” List comprehensions, “iterators”, guards, could all be expressed in the pure func onal language with no magic extensions.
The IO monad is magic. But all the monadic combinators for manipula ng it, sequencing, etc., are not magic; they are expressible in the pure func onal language. HASKELL EFFECTS: REFERENCES Coming Back to References Again • Remember this OCaml func on: let x = ref 0 foo : int -> int
let foo (y:int) : int = x := !x + 1; arg + !x; • We no ced that: let y = foo 3 in foo 3 + foo 3 : int y + y ≠ • What if we write something similar in Haskell? Coming Back to References Again • Remember this OCaml func on: let x = ref 0 foo : int -> int
let foo (y:int) : int = x := !x + 1; arg + !x; • We no ced that: let y = foo 3 in foo 3 + foo 3 : int y + y ≠ • What if we write something similar in Haskell? x :: Ref int doesn't type check
foo :: int -> int foo y = x := read x + 1; arg + !x; Haskell Types Tell You Where the Effects Aren't!
Haskell func on types are pure -- totally effect-free
foo :: int -> int
Haskell’s type system forces* purity on func ons with type a -> b • no prin ng • no mutable data • no reading from files • no concurrency • no benign effects (like memoiza on)
* except for unsafePerformIO exp :: int Same with expressions. Expressions with just type int have no effect! Haskell Types Tell You Where the Effects Aren't!
foo :: int -> int totally pure func on
suspended (lazy) computa on :: IO int that performs effects when executed Haskell Types Tell You Where the Effects Aren't!
foo :: int -> int totally pure func on
suspended (lazy) computa on :: IO int that performs effects when executed
bar :: int -> IO int totally pure func on that returns suspended effec ul ac on Haskell Types Tell You Where the Effects Aren't!
print :: string -> IO ()
reverse :: string -> string
reverse “hello” :: string
print (reverse “hello”) :: IO ()
the type system always tells you when an effect has happened – effects can’t “escape” the I/O monad References
read :: Ref a -> IO a
(+) :: int -> int -> int r :: Ref int
(read r) + 3 :: int
Doesn’t type check References
read :: Ref a -> IO a
(+) :: int -> int -> int
r :: Ref int
do x <- read r return (x + 3)
creates a composi on ac on that reads a reference and the "return" ac on has no effect; produces the value in that reference it just returns its value plus 3 new :: a -> IO (Ref a) Mutable State read :: Ref a -> IO a write :: Ref a -> a -> IO ()
Haskell uses new, read, and write* func ons within the IO Monad to manage mutable state.
main = do r <- new 0 -- let r = ref 0 in inc r -- r := !r+1; s <- read r -- s := !r; print s
inc :: Ref Int -> IO () inc r = do v <- read r -- v := !r write r (v+1) -- r := !v +1
* actually newRef, readRef, writeRef, … MONADS The Bigger Picture • Haskell's type system (at least the part of it that we have seen so far) is very similar to the ML type system – eg: typing rules for pure primi ves (func ons, pairs, lists, etc) are similar in both cases: • if f : t1 -> t2 and e : t1 then (f e) : t2 • if e1 : t1 and e2 : t2 then (e1, e2) : (t1, t2)
• What Haskell has done that is special is: – it has been very careful to give effec ul primi ves special types involving "IO t" – allows composi on of ac ons with type "IO t" • the IO data type + its composi on func ons are called a monad – it has syntax for programming with monads (do nota on) – ML could do those things too (ie: these decisions do not depend upon a language have lazy evalua on)
We can talk about what monads are by referring to their interface
Recall an interface declares some new abstract types and some opera ons over values with those abstract types. For example:
There are lots of different implementations of such module type CONTAINER = sig containers: queues, stacks, sets, randomized sets, ... type ‘a t (* the type of the container *)
val empty : ‘a t
val insert : ‘a -> ‘a t -> ‘a t
val remove : ‘a t -> ‘a op on * ‘a t
val fold : (‘a -> ‘b -> ‘b) -> ‘b -> ‘a t -> ‘b end
We can talk about what monads are by referring to their interface
Recall an interface declares some new abstract types and some opera ons over values with those abstract types. For example:
There are lots of different implementations of such module type CONTAINER = sig containers: queues, stacks, sets, randomized sets, ... type ‘a t (* the type of the container *)
val empty : ‘a t Interfaces can come with some equations one expects every implementation to val insert : ‘a -> ‘a t -> ‘a t satisfy. eg:
val remove : ‘a t -> ‘a op on * ‘a t fold f base empty == base The equations specify some, val fold : (‘a -> ‘b -> ‘b) -> ‘b -> ‘a t -> ‘b but not all of the behavior of the module (eg: stacks and end queues remove elements in different orders)
Monads A monad is just a par cular interface. Two views: – (1) interface for a very generic container, with opera ons designed to support composi on of computa ons over the contents of containers – (2) interface for an abstract computa on that does some “bookkeeping” on the side. By “bookkeeping” we mean “effects”. Once again, the support for composi on is key. – since func onal programmers know that func ons are data, the two views actually coincide
Monads A monad is just a par cular interface. Two views: – (1) interface for a very generic container • supports composi on of computa ons over the contents of containers – (2) interface for an abstract computa on that does some “book keeping.” • bookkeeping is code for “has an effect”. Once again, the support for composi on is key. – since func onal programmers know that func ons are data, the two views actually coincide
Many different kinds of monads: – monads for handling/accumula ng errors – monads for processing collec ons en masse – monads for logging strings that should be printed – monads for coordina ng concurrent threads (see OCaml Async library) – monads for backtracking search – monads for transac onal memory
Monads A monad is just a par cular interface. Two views: – (1) interface for a very generic container • supports composi on of computa ons over the contents of containers – (2) interface for an abstract computa on that does some “book keeping.” • bookkeeping is code for “has an effect”. Once again, the support for composi on is key. – since func onal programmers know that func ons are data, the two views actually coincide
Because a monad is just a par cular interface (with many useful implementa ons), you can implement monads in any language – But, Haskell is famous for them because it has a special built-in syntax that makes monads par cularly easy and elegant to use – F#, Scala have adopted similar syntac c ideas – Monads also play a very special role in the overall design of the Haskell language due to the confinement of effects • Monads are the right way to enable both effects and subs tu on of equals for equals
What is the monad interface? module type MONAD = sig
type ‘a M + some equa ons specifying val return : ‘a -> ‘a M how return and bind are required to interact val (>>=) : ‘a M -> (‘a -> ‘b M) -> ‘b M end
Consider first the “container interpreta on”: § ‘a M is a container for values with type ‘a § return x puts x in the container § bind c f takes the values in c out of the container and applies f to them, forming a new container holding the results - bind c f is o en wri en as: c >>= f
The Op ons as a Container module type MONAD = sig module Op onMonad = struct
type ‘a M type ‘a M = ‘a op on
val return : ‘a -> ‘a M let return x = Some x
val (>>=) : ‘a M -> (‘a -> ‘b M) -> ‘b M let (>>=) c f = match c with end None -> None | Some v -> f v
end
put value in a container take value v out of a container c and then apply f, producing a new container The Op ons as a Container
module type MONAD = sig module Op onMonad = struct
type ‘a M type ‘a M = ‘a op on
val return : ‘a -> ‘a M let return x = Some x
val (>>=) : ‘a M -> (‘a -> ‘b M) -> ‘b M let (>>=) c f = match c with end None -> None | Some v -> f v using the option container: type file_name = string end val read_file : file_name -> string M put value in a container let concat f1 f2 = take value v out readfile f1 >>= (fun contents1 -> of a container c readfile f2 >>= (fun contents2 -> and then apply f, return (contents1 ^ contents2) producing a new container Book-keeping Interpreta on: The Op on Monad as Possibly Erroneous Computa on module type MONAD = sig module ErrorMonad = struct
type ‘a M type ‘a M = ‘a op on
val return : ‘a -> ‘a M let return x = Some x
val (>>=) : ‘a M -> (‘a -> ‘b M) -> ‘b let (>>=) c f = M match c with None -> None end | Some v -> f v using the error monad: type file_name = string end val read_file : file_name -> string M compose bookkeeping: setting up let concat f1 f2 = check to see if bookkeeping readfile f1 >>= (fun contents1 -> error has occurred, for error readfile f2 >>= (fun contents2 -> if so return None, processing return (contents1 ^ contents2) else continue Lists as Containers module type MONAD = sig module ListMonad = struct type ‘a M type ‘a M = ‘a list val return : ‘a -> ‘a M let return x = [x] val (>>=) : ‘a M -> (‘a -> ‘b M) -> ‘b M let (>>=) c f = end List.fla en (List.map f c) using the list monad: end random_sample : unit -> int M monte_carlo : int -> int -> int -> result put element let experiments : result M = apply f to all elements into list random_sample() >>= (fun s1 -> of the list c, creating a container random_sample() >>= (fun s2 -> list of lists and then random_sample() >>= (fun s3 -> flatten results in to return (monte_carlo s1 s2 s3) single list Lists as Containers module type MONAD = sig module ListMonad = struct type ‘a M type ‘a M = ‘a list val return : ‘a -> ‘a M let return x = [x] val (>>=) : ‘a M -> (‘a -> ‘b M) -> ‘b M let (>>=) c f = end List.fla en (List.map f c) using the list monad: end random_sample : unit -> int M monte_carlo : int -> int -> int -> result one result; let experiments : result M = compose many no nondeterminism random_sample() >>= (fun s1 -> possible results (c) random_sample() >>= (fun s2 -> with a nondeterministic random_sample() >>= (fun s3 -> continuation f return (monte_carlo s1 s2 s3) A Container with a String on the Side (aka: A logging/prin ng monad) module type MONAD = sig module LoggingMonad = struct
type ‘a M type ‘a M = ‘a * string
val return : ‘a -> ‘a M let return x = (x, “”)
val (>>=) : ‘a M -> (‘a -> ‘b M) -> ‘b M let (>>=) c f = let (v, s) = c in end let (v’,s’) = f v in using the logging monad: (v’, s ^ s’) record : (‘a -> ‘b) -> ‘a -> string -> ‘b M end let record f x s = (f x, s) nothing logged let do x = concatenate the yet record read x “read it” >>= (fun v -> log of c with record write v “wrote it” >>= (fun _ -> the log produced record write v “wrote it again” >>= (fun _ -> by running f return v A Container with a State on the Side (aka: A logging/prin ng monad) module type MONAD = sig module StateMonad = struct
type ‘a M type state = address int map
val return : ‘a -> ‘a M type ‘a M = ‘a * (state -> state)
val (>>=) : ‘a M -> (‘a -> ‘b M) -> ‘b M let return x = ??? end let (>>=) c f = ???
let read a = ???
let write a v = ???
end Monad Laws Just like one expects any CONTAINER to behave in a par cular way, one has expecta ons of MONADs.
Le iden ty: “return does nothing observable” (1) return v >>= f == f v
Right iden ty: “return s ll doesn’t do anything observable” (2) m >>= return == m
Associa vity: “composing m with f first and then doing g is the same as doing m with the composi on of f and g” (3) (m >>= f) >>= g == m >>= (fun x -> f x >>= g) Breaking the Law Just like one expects any CONTAINER to behave in a par cular way, one has expecta ons of MONADs.
Le iden ty: “return does nothing observable” (1) return v >>= f == f v
return 3 >>= fun x -> return x module LoggingMonad = struct == (3,”start”) >>= fun x -> return x
== (3, “start” ^ “start”) type ‘a M = ‘a * string == (3, “startstart”)
let return x = (x, “start”)
let (>>=) c f = let (v, s) = c in (fun x -> return x) 3 let (v’,s’) = f v in == return 3 (v’, s ^ s’) == (3, “start”) end Breaking the Law What are the consequences of breaking the law?
Well, if you told your friend you’ve implemented a monad and they can use it in your code, they will expect that they can rewrite their code using equa ons like this one: return x >>= f == f x
If you tell your friend you’ve implemented the monad interface but none of the monad laws hold your friend will probably say: Ok, tell me what your func ons do then and please stop using the word monad because it is confusing. It is like you are claiming to have implemented the QUEUE interface but insert and remove are First-In, First-Out like a stack.
In Haskell or F# or Scala, breaking the monad laws may have more severe consequences, because the compiler actually uses those laws to do some transforma ons of your code. By now you should appreciate the significance of Haskell’s logo, combining the lambda λ and the monadic bind operator >>=.
HASKELL WRAPUP A Monadic Skin
In languages like ML or Java, there is no way to dis nguish between: – pure func ons with type int -> int – effec ul func ons with type int -> int
In Haskell, the programmer can choose when to live in the IO monad and when to live in the realm of pure func onal programming. – Counter-point: We have shown that it is useful to be able to build pure abstrac ons using impera ve infrastructure (eg: laziness, futures, parallel sequences, memoiza on). You can’t do that in Haskell (without escaping the type system via unsafeI0)
Interes ng perspec ve: It is not Haskell that lacks impera ve features, but rather the other languages that lack the ability to have a sta cally dis nguishable pure subset.
A checked pure-impure separa on facilitates reasoning about programs, especially concurrent ones. The Central Challenge
Useful Arbitrary effects
No effects Useless
Dangerous Safe The Challenge of Effects
Plan A (everyone else) Useful Arbitrary effects Nirvana
Plan B (Haskell)
No effects Useless
Dangerous Safe Two Basic Approaches: Plan A
Arbitrary effects
Examples Default = Any effect Plan = Add restrictions • Regions • Ownership types • Rust – following from research languages like Vault, Spec#, Cyclone Two Basic Approaches: Plan B
Default = No effects Plan = Selectively permit effects
Types play a major role
Two main approaches: • Domain specific languages (SQL, Xquery, Google map/ reduce) • Wide-spectrum func onal Value oriented languages + controlled effects programming (e.g. Haskell) Lots of Cross Over
Plan A (everyone else) Useful Arbitrary effects Nirvana
Envy Plan B (Haskell)
according to Simon Peyton Jones No effects Useless
Dangerous Safe Lots of Cross Over
Plan A (everyone else) Useful Arbitrary effects Nirvana
Ideas; e.g. Software Transactional Memory Plan B (Haskell)
No effects Useless
Dangerous Safe Are we there yet?
Useful Is Haskell here? Nirvana
Or here? Plan B Some issues: (Haskell)
• Can’t encapsulate effec ul computa on inside pure- func onal types. No effects • Lazy thunks can give a Useless performance penalty
Dangerous Safe An Assessment and a Predic on
One of Haskell’s most significant contributions is to take purity seriously, and relentlessly pursue Plan B.
Imperative languages will embody growing (and checkable) pure subsets.
-- Simon Peyton Jones