For my own benefit (see title of blog) as much as anything else. When I wrote
Rule 1 of monadic parsers is -- if you return
Monad.Zero, you're probably doing it wrong... It's likely that you mean monadic return of an empty list.
it's because there's a distinction between positively returning an empty result (i.e. successfully doing nothing) and returning nothing at all (not succeeding in what you were trying to do) -- as the F# documentation for
Zero puts it
Called for empty else branches of if...then expressions in computation expressions.
So, for a parser monad
Zero is the function that returns a parser that takes no input and always fails:
Seq monad, it's the empty sequence, for
None and so forth -- basically the only thing you can return if you don't have any value to return.
And for individual leaf parsers -- the functions that define an
Operation<TInput, TOutput> and return them wrapped as a
Parser<TInput, TOutput>, those
Operation<TInput, TOutput>s should return the same non-value when faced with a parse failure.
By contrast, when composing a parser that may try to consume some of the input, but fail, and you want to not fail the whole parse, you need a parser that successfully matches nothing (and consumes nothing) as an alternative that provides a success value -- and that is not
Zero, but rather
Return of the non-value of the appropriate type. So, for example, the parser combinator
is "Consume as many blocks of
p as you can -- or, if that fails, do nothing"; by contrast, the combinator
Sat fails if the predicate doesn't match:
So in that case it says
Zero and means
Zero because that's a failure to satisfy the parser -- here in the equivalent the F# computation expression we don't even have to write the
else branch; it's filled in for us by the compiler.
And that's really the one subtlety to be borne in mind -- otherwise you end up with an almost intractable debugging issue when your parser fails unexpectedly, partway through consuming valid input.