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
Traversableinstance would be used for operations that do something on each element of the structure while maintaining same structure in the output. AFoldableinstance would be used for collapsing the elements into anything else. ↩