In learning Parsec, I've found that somewhat verbose rules like
type PhonemeClassMap = Map Char String
type ContextElement = Parser String
phonemeContext :: Parsec String PhonemeClassMap ContextElement
phonemeContext = do
c <- lower
return $ char c
can be simplified by lifting functions like char
into the Parsec
/ ParsecT
monad.
phonemeContext :: Parsec String PhonemeClassMap ContextElement
phonemeContext = liftM char lower
Now I'm trying to simplify a rule which modifies the user state:
import Data.Map (insert)
phonemeClassDefinition :: Parsec String PhonemeClassMap ()
phonemeClassDefinition = do
upperChar <- upper
lowerChars <- char ':' >> spaces >> many1 lower
modifyState (insert upperChar lowerChars)
I can easily lift insert :: Char -> String -> PhonemeClassMap -> PhonemeClassMap
to make the following improvement:
phonemeClassDefinition = do
f <- liftM2 insert upper (char ':' >> spaces >> many1 lower)
modifyState f
Is there any way to state these two expressions as one? The same lifting technique does not work for modifyState :: Monad m -> (u -> u) -> ParsecT s u m ()
.
In this case, you are looking for monadic bind >>= :: Monad m => (a -> m b) -> m a -> m b
which allows you to apply a function that takes a pure a
and returns a monadic action to a monadic a
(i.e. apply the function "through" the monad). This function is actually an integral part of the monadic type class, and is what the <-
in do
notation desugars to under the hood.
(Side note, unlike liftM2
, liftM3
..., there doesn't appear to be predefined bindM2 :: Monad m => (a -> b -> m c) -> m a -> m b -> m c
(or bindM3
etc) for convenience. (Hoogle draws a blank.))
Also, Parsec parsers often benefit (stylistically and otherwise) from using its Applicative and Functor instances, not just its Monad one, specifically <$>
(alias of fmap
/liftM
) and the various (semi-)equivalents of monadic ap
& >>
: <*>
, <*
& *>
.
phonemeContext = char <$> lower
phonemeClassDefinition = (insert <$> upper <*> (char ':' *> spaces *> many1 lower)) >>= modifyState
(Note that @melpomene's =<<
is just flip (>>=)
, i.e. with the arguments swapped.)