>> Is Still >>=

>> Is Still >>=

I have spent the entire morning puzzling over a piece of code in Programming in Haskell by Graham Hutton ( a good book for newbie Haskellers and as it happens intermediates too).

Here is the offending piece of code:

string :: String -> Parser String
string []       = return []
string (x:xs) = do char x
                   string xs
                   return (x:xs)

Without going into too much detail, char returns a parser which succeeds if it tries to parse the character x and fails otherwise.

In its monadic context, a parser halts when failure is met so the following code:

parser = char `a` >>= \x -> ...
parse parser "boo"

Will fail at the first step.

The expected behaviour of string is as follows:

parse (string "abc") "abcdef" -- => [("abc","def")]

The parser returrns the parsed result on the left hand side of a tuple and any unconsumed characters on the right.

I was stumped by the code for string! How did the parsers compose? I had thought that >> meant “ignore my result and keep going”.

Well, I was half right. >> does mean “ignore my result and keep going”, but it does not mean “ignore my effect and keep going”.

As you are probably aware, Applicative and by extension Monad are for handling programming with effects. By effects we really mean side-effects. When you chain effectful computations together in Haskell (most notably IO) you are writing code that returns a value but does something else at the same time, like writing to a disk or reading from input. The different monads in Haskell all provide a way of chaining effectful computations together such that the side effects are taken care of for you. There’s lots of different ways that “side effect” can be interpreted, and it depends on the underlying type which the monad is constructed from, but they all have a concept of “failure”. If a computation fails then the monadic chain stops at that point and the failure “value” is returned. For instance:

success = \n -> Just (n + 2)
fail = \n -> Nothing
Just 1 >>= success -- => Just 3
Just 1 >>= fail >>= success -- => Nothing

Now, after spending a lot of time using the IO monad I was used to writing code like this:

main = getArgs >>= 
       print >>
       print "Printed the args!"

What had been going on in my head was “get the args and print them, ignore whatever was returned, and then print “Printed the args!”“. To me, >> was an operator that meant “do this and forget about it”. To me these intermediate steps had been defined only in terms of the fact that they were subsidiary to the main computation. The could effectively be ignored as a side matter.

It should have been obvious, but of course if getArgs or print had failed then the IO monad would have thrown an exception (i.e. failed) and the chain would have halted before printing “Printed the args!”. In other words, the effect would not have been ignored.

>> says: Run this computation, if it succeeds pass on to the next computation without the result. Otherwise return failure.
>>= says: Run this computation. If it succeeds pass the returned value on to the next computation. Otherwise return failure.

So how does string work?

It returns a parser, which will attempt to parse each character it reads. If any of the parsers fail then the computation will halt. Otherwise the computation passes on to the next parser. If all the parsers succeed then the original string is returned.

Written with StackEdit.

Comments

Popular Posts