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)