0

I have been reading a tutorial about parser combinators and I came across a function which I would like a bit of help in trying to understand.

satisfy :: (Char -> Bool) -> Parser Char
satisfy p = item `bind` \c ->
  if p c
  then unit c
  else (Parser (\cs -> []))

char :: Char -> Parser Char
char c = satisfy (c ==)

natural :: Parser Integer
natural = read <$> some (satisfy isDigit)

string :: String -> Parser String
string [] = return []
string (c:cs) = do { char c; string cs; return (c:cs)}

My question is how does the string function work or rather how does it terminate, say i did something like:

let while_parser = string "while"

and then i used it to parse a string say for example parse while_parser "while if" , it will correctly parse me the "while".

however if i try something like parse while_parser "test it will return [].

My question is how does it fail? what happens when char c returns an empty list?

4
  • I suspect char c doesn't "return an empty list", rather it fails on end of input. The bind operator then propagates that failure. Commented Aug 16, 2016 at 13:20
  • You meant let while_parser = string "while", right? Commented Aug 16, 2016 at 13:49
  • @MathematicalOrchid From the definition of satisfy when char fails it will return a function which generates an empty list. What do you mean by propagate failure? Commented Aug 16, 2016 at 14:09
  • @sepp2k ah yes thank you! Commented Aug 16, 2016 at 14:10

1 Answer 1

1

Let's say your Parser is defined like this:

newtype Parser a = Parser { runParser :: String -> [(a,String)] }

Then your Monad instance would be defined something like this:

instance Monad Parser where
  return x = Parser $ \input -> [(x, input)]
  p >>= f = Parser $ \input -> concatMap (\(x,s) -> runParser (f x) s) (runParser p input)

You're wondering what happens when char c fails in this line of code:

string (c:cs) = do { char c; string cs; return (c:cs) }

First, let's desugar it:

string (c:cs) = char c >>= \_ -> string cs >>= \_ -> return (c:cs)

Now the part of interest is char c >>= \_ -> string cs. From the definition of char and subsequently the definition of satisfy we see that ultimately runParser (char c) input will evaluate to [] when char c fails. Look at the definition of >>= when p is char c. concatMap won't have any work to do because the list will be empty! Thus any calls to >>= from then on will just encounter an empty list and pass it along.

One of the wonderful things about referential transparency is that you can write down your expression and evaluate it by substituting definitions and doing the function applications by hand.

Sign up to request clarification or add additional context in comments.

4 Comments

Technically, it desugars to char c >> string cs >> return (c:cs), since the desugaring is independent of whatever monad is in use. m >> f is usually, but isn't required to be, implemented as m >>= \_ -> f.
@chepner Yep. I was too lazy to explain a tangential detail.
thank you, i think i kind of get it, but where does the recursive call to string fit in to all of this?
Note that in the definition of >>=, the call to f (which is \_ -> string cs in this example) is inside the function passed to concatMap. But since (runParser p input) is an empty list, that function is never actually used. Thus, the recursion doesn't happen.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.