Improving readability with the Maybe Foldable instance
One recommendation you often hear when reaching an acceptable level of basic
Haskell is to make your code more polymorphic. The Haskell Prelude
is
heavily biased towards lists, so an immediate gain in polymorphism is to make
your code work not only for lists but for any instance of Traversable
or
Foldable
.1
Since a Haskell list is an instance of Traversable
and Foldable
, we can
still operate as usual on lists with the new polymorphic code:
>>> import qualified Data.Foldable as F
>>> mapM_ print ['a'..'c']
'a'
'b'
'c'
>>> F.traverse_ print ['a'..'c']
'a'
'b'
'c'
Notice here that we’ve gained the extra advantage of being able to use Applicative
instead of Monad
. Here, we don’t need the extra power of Monad
, and the
Applicative
, by being weaker, is also more generalizable.
But aside of lists, there is another instance of Traversable
/Foldable
defined by default for us: Maybe
. You could think of a Maybe
as a list of 1
or 0 elements, so when you are traversing it you either do something with the
element if present or do the default Applicative
/Monad
action (pure
and
return
respectively) if not present. How is this useful then? Have you ever
found yourself writing case expressions like this?
>>> :{
>>> let printM m = case m of
>>> Just n -> print n
>>> Nothing -> return ()
>>> :}
>>> let m1 = Just 1 :: Maybe Int
>>> let m_ = Nothing :: Maybe Int
>>> printM m1
>>> 1
>>> printM m_
>>>
The function maybe
would improve a bit, syntactically speaking: maybe
(return ()) print
.
Maybe it’s just me, but that return ()
smells too much of a default
behavior to me. Somehow, there should be a way to avoid it. Well, here is where
Foldable
instance of Maybe
comes handy:
>>> :set -XScopedTypeVariables
>>> let printM' :: Maybe Int -> IO () = F.traverse_ print
>>> printM' m1
>>> 1
>>> printM' m_
>>>
To be fair, for this trivial example, it would be a bit frivolous to use the
Maybe
Foldable
instance just to avoid the case expression, but when you are
in an intricate case expression ladder, this idiom can make your code much more
readable.
-
Roughly speaking, the
Traversable
instance would be used for operations that do something on each element of the structure while maintaining same structure in the output. AFoldable
instance would be used for collapsing the elements into anything else. ↩