Talk:Haskell/Solutions/Practical monads

i think solutions to exercises are needed if this is also intended for (unsupervised) beginners. i have played around a bit with the first box of exercises and hereby submit some solutions for review (as a full source file tested with ghci 6.2.1). even if they are substandard, maybe someone can come up with amendments. i tried to switch between do and >>= notation at some places for didactic purposes. Rsling (talk) 15:38, 6 November 2008 (UTC)

import System import Text.ParserCombinators.Parsec hiding (spaces) import Monad import Numeric import Char

-- I replaced the main IO with an IO that can be -- invoked comfortably from ghci. Uncomment `main' -- if you're compiling.

{- main :: IO - main = do args <- getArgs -          putStrLn (readExpr (args !! 0)) -}

-- This allows you to type > pr at ghci prompt -- and then interactively parse something (not needed -- when compiling). It loops. To stop parsing, enter .e

pr :: IO pr = do   putStrLn "Enter something to parse" putStrLn "(.e to stop parsing):" i <- getLine case i of            ".e" -> putStrLn "End of parsing" otherwise -> do putStrLn (readExpr i)                            pr

-- Verbatim from the Wiki book (with changes from exercises):

symbol :: Parser Char symbol = oneOf "!$%&|*+-/:<=>?@^_~"

spaces :: Parser spaces = skipMany1 space

parseString :: Parser LispVal parseString = do char '"'                x <- many (noneOf "\"") char '"'                return $ String x

parseAtom :: Parser LispVal parseAtom = do first <- letter <|> symbol rest <- many (letter <|> digit <|> symbol) let atom = [first] ++ rest return $ case atom of                          "#t" -> Bool True "#f" -> Bool False otherwise -> Atom atom

parseNumber :: Parser LispVal parseNumber = liftM (Number . read) $ many1 digit

parseExpr :: Parser LispVal parseExpr = parseAtom <|> parseStringEsc <|> parseDecFloat -- changed ex. 1 // again ex. 6 <|> parseNumSigF -- added ex. 2 + 3 // changed ex. 6

-- Slightly amended portions from the Wiki book:

data LispVal = Atom String | List [LispVal] | DottedList [LispVal] LispVal | Number Integer | String String | Bool Bool | Char Char -- added ex. 5 | Float Float -- added ex. 6 deriving (Eq, Show)

-- This readExpr accepts input either with trailing spaces or without -- (the text and the examples therein are not consistent wrt this point). -- It can be simplified to (spaces <|> parseExpr) for later exercises. -- -- Also, if there is a match, the matching expression is printed.

readExpr :: String -> String readExpr input = case parse ((spaces >> parseExpr) <|> parseExpr) "MyLang" input of	Left err -> "No match: " ++ show err Right val -> "Match: " ++ show val

-- -- -- -- -- -- -- -- -- EXERCISES PROPER --- -- -- -- -- -- -- -- --

-- Ex.1

parseNumber2 :: Parser LispVal parseNumber2 = do x <- (many1 digit) return $ (Number . read) x

parseNumber3 :: Parser LispVal parseNumber3 = (many1 digit) >>= \x -> return ((Number. read) x)

-- Ex. 2 + 3 -- -- The string parser must be extended to fail on " and \, -- then continuing parsing (<|>) with parseQuoted (see below).

parseStringEsc :: Parser LispVal parseStringEsc = do char '"'                   x <- many ((noneOf "\"\\") <|> parseQuoted) char '"'                   return $ String x

-- We define a parser action that recognizes \" and returns ": -- It ignores one \ and returns the following character if its -- one of the R5RS quoted chars. It fails if there is a quoted -- nonsensical char like \j, which should be sensible behavior.

parseQuoted :: Parser Char parseQuoted = do char '\\' x <- oneOf "\"\\nrt"		 case x of                  '\\' -> return x                   '\"' -> return x                   'n' -> return '\n' 'r' -> return '\r' 't' -> return '\t'

-- Ex. 4 + 5 -- -- 1. Leave `bare' integer recognition as it is (parseNumber). -- 2. Up to here, #-trailing sequences are not recognized at all. - Good! -- 3. Write parseNumSign parser which reads and skips a #, --   then recognizes b o d x, then reads the remaining hex-digits into x, --    returning the constructor and the Haskell value using readOct etc. -- 4. For ex. 5, istead of "bodx", we recognize '\\', then read the --   next Char (first) plus the remaining letters (long literals --    only contain letters) (rest). If the concatenation (++) is a long --   literal (only "space" and "newline" here), the relevant --   Haskell literals are returned, otherwise just the character read --   as first.

parseNumSign :: Parser LispVal parseNumSign = do char '#' x <- oneOf "xobd\\" case x of                   'x' -> do y <- many hexDigit return $ Number (fst (head (readHex y))) 'o' -> do y <- many octDigit return $ Number (fst (head (readOct y))) 'b' -> do y <- many binDigit return $ Number (readBin y)                   'd' -> do y <- many digit return $ Number (read y)                   '\\' -> do first <- anyChar rest <- many letter case ([first] ++ rest) of                                "space" -> return $ Char ' ' "newline" -> return $ Char '\n' otherwise -> return $ Char first

-- I couldn't find any standard readBin and binDigit. So here they are:

binDigit :: Parser Char binDigit = oneOf "01"

-- The following point-free one-liner is from -- http://tehgeekmeister.wordpress.com/2008/01/11/one-line-binary-reader-in-haskell/

readBin = (`div` 2). foldl (\x y -> (x + y) * 2) 0. map (\c -> case c of {           '0' -> 0; '1' -> 1; _ -> error "Input is not a binary string."})

-- Ex. 6 -- -- [A complete rewrite of the previous parseNumSig for didactic purposes.] -- I skip the implementation of #e and #i from R5RS, just plain floats. -- Also, the do notation was replaced by applying >>= notation where -- possible, and `case of' blocks are written using brackets. Thus, we get -- around the annoying alignment conventions.

parseNumSigF :: Parser LispVal parseNumSigF = char '#' >> oneOf "xobd\\" >>= \x -> case x of              { 'x' -> many hexDigit >>= \y -> return $ Number (fst (head (readHex y))); 'o' -> many octDigit >>= \y -> return $ Number (fst (head (readOct y))); 'b' -> many binDigit >>= \y -> return $ Number (readBin y); 'd' -> parseDecFloat >>= \y -> return y;                '\\' -> anyChar >>= \first -> many letter >>= \rest -> case ([first] ++ rest) of                          { "space" -> return $ Char ' '; "newline" -> return $ Char '\n'; elsewise -> return $ Char first }              }

parseNumSigFDo :: Parser LispVal parseNumSigFDo = do char '#' x <- oneOf "xobd\\" case x of                     'x' -> do y <- many hexDigit return $ Number (fst (head (readHex y))) 'o' -> do y <- many octDigit return $ Number (fst (head (readOct y))) 'b' -> do y <- many binDigit return $ Number (readBin y)                     'd' -> do y <- parseDecFloat return y                     '\\' -> do first <- anyChar rest <- many letter case ([first] ++ rest) of                                  "space" -> return $ Char ' ' "newline" -> return $ Char '\n' otherwise -> return $ Char first

-- This is the actual float parser. It take n digits, a '.' and -- m digits and, if found, returns their concatenation read as a Float -- Given in do notation and >>= notation.

parseFloatDo :: Parser LispVal parseFloatDo = do x <- many digit y <-char '.' z <- many digit return $ Float (fst (head (readFloat (x++[y]++z))))

parseFloat :: Parser LispVal parseFloat = many digit >>= \x -> char '.' >>= \y -> many digit >>= \z -> return $ Float (fst (head (readFloat (x++[y]++z))))

-- This is the parser for dec numerals: float *or* int. -- If it fails to recognize a Float, it tries to recognize an Int. -- (Backtracking is done by the try combinator, it is the subject of -- the *next* section of the book. However, this is much easier -- to solve with backtracking.)

parseDecFloat :: Parser LispVal parseDecFloat = (try parseFloat) <|> parseNumber3

-- Ex. 7 -- Somebody, plz!