module MonadExamples where import ParseLib.Abstract hiding ((<*), (*>), sequence) import Data.Char (isSpace) {- The Monad class definition: class Monad m where return :: a -> m a (>>=) :: m a -> (a -> m b) -> m b The instance for a Parser: instance Monad (Parser s) where return :: a -> Parser s a return = succeed (>>=) :: Parser s a -> (a -> Parser s b) -> Parser s b p >>= f = \xs -> concatMap (\(r,ys) -> f r ys) (p xs) The parser p gives a list of successes (pairs of result and remaining input). >>= applies the function f to each of those results to get a new parser, and evaluates that parser on the remaining input. We use 'concatMap' (the >>= for lists) to flatten this list-of-lists-of-successes into a single list of successes. -} -- As seen in the lecture, we can use >>= to validate certain properties in our parser. -- With the monad instance, we could even use do-notation: hours1 is the same as hours2. hours1,hours2 :: Parser Char Int hours1 = natural >>= \x -> if x < 24 then succeed x else empty hours2 = do x <- natural -- <- translates to >>= if x < 24 then return x -- return == pure == succeed else empty -- For didactic purposes, we try not to use do-notation too much. -- Once you understand what is 'behind' it, it can be a very elegant way to express monadic code, including parsers! -- Another example of the use of this >>= primitive is in the parser for Spaces in practical 2. -- We first parse a pair of numbers, which tell us the grid size, and then use those numbers to parse the grid. -- A simplified example here: We parse 1 number, and then parse that many other numbers: pSizedList :: Parser Char [Int] pSizedList = natural -- parse the size <* spaces -- discard whitespace >>= \size -> -- use the size to build a new parser for the rest of the input sequence -- collapse a list of parsers into a parser of a list (replicate size -- repeat the following parser `size` times (natural <* spaces)) -- parse a number and discard whitespace -- sizedList = [([10,11,12,13,14],"15")]: pSizedList parses exactly 5 of the numbers that follow the '5'. -- If there are more, they do not get consumed, and if there are less, the parser fails ([]). sizedList :: [([Int], [Char])] sizedList = parse pSizedList "5 10 11 12 13 14 15" -- Note that we consider it 'bad style' to use the Monad interface (>>= or do) when it is not needed: bad,good :: Parser Char String bad = do x <- anySymbol if x == 'a' then token "hello" else if x == 'b' then token "bye" else empty good = symbol 'a' *> token "hello" <|> symbol 'b' *> token "bye" -- Usually, just <$>, <*>, and <|> will be enough to work with. When you do need it, >>= can be very useful! spaces :: Parser Char String spaces = greedy1 (satisfy isSpace)