filter and its monadic cousin
This is my attempt to understand the relevance of the differences between these two functions. Please leave a comment to let me know about anything I’ve misunderstood. :)
We can use the non-monadic
filter to get a list of even numbers, or use
fmap to apply this filter inside a monad (or any functor):
filterM form gives us something
filter can’t, and that’s allowing us to interact with a monadic context.
For example, say we want to filter out even numbers from an
[Int], but we don’t want to return anything if any number is less than or equal to 0 (perhaps we want to call a function that only works with positive integers).
filter takes a predicate
(a -> Bool) which can only evaluate to
False for each element – there is no way for it to interact with a monadic environment, even if it is mapped into one. Nor can
fmap for that matter, as it works with functions
(a -> b) and preserves the structure it is mapping over.
filterM doesn’t have this restriction due to its
(a -> m Bool) argument, which in this case can produce
Just False, or stop the computation by evaluating to
We can apply a similar filter function to a monadic parser.
If we have an instance of a
Parser [Int] that parses comma-separated numbers, we can filter even numbers like in our previous example:
Now we want to filter even numbers from our parser, but we want to stop parsing if we get 0 or a negative number. We can use
filterM to construct a new parser from our
csv parser that does this:
Another example from Tony Morris’s Haskell exercises is getting a distinct list of elements using the State monad with a
Data.Set to keep track of which elements have been seen:
filterM ensures that the state (the contents of the
Data.Set) is preserved as each element in the source list is traversed.
Say we want to filter a list of files paths to only include files that exist. This requires checking the file system, which requires a
FilePath -> IO Bool function called
What hadn’t really twigged for me previously is that
filterM’s first argument
(a -> m Bool) lets us do standard filtering like
filter, but gives us the additional option to keep some monadic effect while filtering, such as canceling a computation with
Nothing or by failing parsing, or updating some state. In contrast, mapping
filter inside a monad does not give us this ability to interact with that effect1.
I think this limitation can also be a big advantage. Using
fmapmeans we know we aren’t going to interacting with a monadic context, so we can safely transform an
m awithout being concerned about ill effects.↩