Haskell: The Craft of Functional Programming Simon Thompson (c) Addison-Wesley, 1999. Chapter 18 Programming with actions ^^^^^^^^^^^^^^^^^^^^^^^^ > module Chapter18 where > import Prelude hiding (lookup) > import IO -- for isEOF (see note below, aslo) > isEOF = hugsIsEOF -- this should be commented out in later > -- versions; it is here because Hugs 1.4 > -- doesn't support isEOF The basics of input/output ^^^^^^^^^^^^^^^^^^^^^^^^^^ Reading input is done by getLine and getChar: see Prelude for details. getLine :: IO String getChar :: IO Char Text strings are written using putStr :: String -> IO () putStrLn :: String -> IO () A hello, world program > helloWorld :: IO () > helloWorld = putStr "Hello, World!" Writing values in general print :: Show a => a -> IO () The do notation: a series of sequencing examples. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Put a string and newline. putStrLn :: String -> IO () putStrLn str = do putStr str putStr "\n" Put four times. > put4times :: String -> IO () > put4times str > = do putStrLn str > putStrLn str > putStrLn str > putStrLn str Put n times > putNtimes :: Int -> String -> IO () > putNtimes n str > = if n <= 1 > then putStrLn str > else do putStrLn str > putNtimes (n-1) str Read two lines, then write a message. > read2lines :: IO () > read2lines > = do getLine > getLine > putStrLn "Two lines read." Read then write. > getNput :: IO () > getNput = do line <- getLine > putStrLn line Read, process then write. > reverse2lines :: IO () > reverse2lines > = do line1 <- getLine > line2 <- getLine > putStrLn (reverse line2) > putStrLn (reverse line1) Last example redefined to use a local definition. > reverse2lines' :: IO () > reverse2lines' > = do line1 <- getLine > line2 <- getLine > let rev1 = reverse line1 > let rev2 = reverse line2 > putStrLn rev2 > putStrLn rev1 Reading an Int. > getInt :: IO Int > getInt = do line <- getLine > return (read line :: Int) Iteration and recursion ^^^^^^^^^^^^^^^^^^^^^^^ A while loop. > while :: IO Bool -> IO () -> IO () > while test action > = do res <- test > if res then do action > while test action > else return () Copying input to output. > copyInputToOutput :: IO () > copyInputToOutput > = while (do res <- isEOF > return (not res)) > (do line <- getLine > putStrLn line) An important example: refer to the text to see why it fails to work as required. (The incorrect version is primed.) > goUntilEmpty' :: IO () > goUntilEmpty' > = do line <- getLine > while (return (line /= [])) > (do putStrLn line > line <- getLine > return ()) The correct program: the key is to think recursively. > goUntilEmpty :: IO () > goUntilEmpty > = do line <- getLine > if (line == []) > then return () > else (do putStrLn line > goUntilEmpty) Adding a sequence of integers > sumInts :: IO Int > sumInts > = do n <- getInt > if n==0 > then return 0 > else (do m <- sumInts > return (n+m)) Addiing a sequence of integers, courteously. > sumInteract :: IO () > sumInteract > = do putStrLn "Enter integers one per line" > putStrLn "These will be summed until zero is entered" > sum <- sumInts > putStr "The sum was " > print sum The calculator ^^^^^^^^^^^^^^ This is available separately. Input and output as lazy lists ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Reverse all the lines in the input. > listIOprog :: String -> String > listIOprog = unlines . map reverse . lines Monads for Functional Programming ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The definition of the Monad class class Monad m where (>>=) :: m a -> (a -> m b) -> m b return :: a -> m a fail :: String -> m a Kelisli composition for monadic functions. (>@>) :: Monad m => (a -> m b) -> (b -> m c) -> (a -> m c) f >@> g = \ x -> (f x) >>= g Some examples of monads ^^^^^^^^^^^^^^^^^^^^^^^ Some examples from the standard prelude. The list monad instance Monad [] where xs >>= f = concat (map f xs) return x = [x] zero = [] The Maybe monad instance Monad Maybe where (Just x) >>= k = k x Nothing >>= k = Nothing return = Just The identity monad > data Id a = Id a > instance Monad Id where > return = Id > (>>=) (Id x) f = f x The parsing monad data SParse a b = SParse (Parse a b) instance Monad (SParse a) where return x = SParse (succeed x) zero = SParse fail (SParse pr) >>= f = SParse (\s -> concat [ sparse (f x) rest | (x,rest) <- pr st ]) sparse :: SParse a b -> Parse a b sparse (SParse pr) = pr A state monad (the state need not be a table; this example is designed to support the example discussed below.) > type Table a = [a] > data State a b = State (Table a -> (Table a , b)) > instance Monad (State a) where > return x = State (\tab -> (tab,x)) > (State st) >>= f > = State (\tab -> let > (newTab,y) = st tab > (State trans) = f y > in > trans newTab) Example: Monadic computation over trees ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A type of binary trees. > data Tree a = Nil | Node a (Tree a) (Tree a) Summing a tree of integers A direct solution: > sTree :: Tree Int -> Int > sTree Nil = 0 > sTree (Node n t1 t2) = n + sTree t1 + sTree t2 A monadic solution: first giving a value of type Id Int ... > sumTree :: Tree Int -> Id Int > sumTree Nil = return 0 > sumTree (Node n t1 t2) > = do num <- return n > s1 <- sumTree t1 > s2 <- sumTree t2 > return (num + s1 + s2) ... then adapted to give an Int solution > sTree' :: Tree Int -> Int > sTree' = extract . sumTree where the value is extracted from the Id monad thus: > extract :: Id a -> a > extract (Id x) = x Using a state monad in a tree calculation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The top level function ... > numTree :: Eq a => Tree a -> Tree Int ... and the function which does all the work: > numberTree :: Eq a => Tree a -> State a (Tree Int) Its structure mirrors exactly the structure of the earlier program to sum the tree. > numberTree Nil = return Nil > numberTree (Node x t1 t2) > = do num <- numberNode x > nt1 <- numberTree t1 > nt2 <- numberTree t2 > return (Node num nt1 nt2) The work of the algorithm is done node by node, hence the function > numberNode :: Eq a => a -> State a Int > numberNode x = State (nNode x) > nNode :: Eq a => a -> (Table a -> (Table a , Int)) > nNode x table > | elem x table = (table , lookup x table) > | otherwise = (table++[x] , length table) Looking up a value in the table; will side-effect the table if the value is not present. > lookup :: Eq a => a -> Table a -> Int > lookup = lookup -- dummy definition: > -- exercise for the reader Extracting a value froma state monad. > extractSt :: State a b -> b > extractSt (State st) = snd (st []) The top-level function defined eventually. > numTree = extractSt . numberTree