Reading production Haskell code, we sometimes stumble over idioms that look confusing at first, but which show up frequently enough that they are worth learning. We have already seen
- how guard–sequence helps us conditionally return something;
- how conditional for helps us execute a side effect only when a value exists; and
- how to tranform an argument before naming it with view patterns.
Today we’ll continue the theme of the previous article, where we try not to give names to intermediary values that are not meant to be used. The way …
Reading production Haskell code, we sometimes stumble over idioms that look confusing at first, but which show up frequently enough that they are worth learning. We have already seen
- how guard–sequence helps us conditionally return something;
- how conditional for helps us execute a side effect only when a value exists; and
- how to tranform an argument before naming it with view patterns.
Today we’ll continue the theme of the previous article, where we try not to give names to intermediary values that are not meant to be used. The way we do this is with the bind operator and the LambdaCase
language extension. This means we write code that says
In[1]:
getQueueMember :: Int -> ExceptT Text QueueAccess Member
getQueueMember position =
lift (Queue.getAt position) >>= \case
Nothing -> throwError "That position holds no member."
Just c -> pure c
Instead of
In[2]:
getQueueMember :: Int -> ExceptT Text QueueAccess Member
getQueueMember position = do
maybeMember <- lift (Queue.getAt position)
case maybeMember of
Nothing -> throwError "That position holds no member."
Just c -> pure c
where the latter leaks an identifier maybeMember
that’s only used in the pattern match. I don’t think this idiom requires very much explanation. It’s useful to burn the comparison above into the right neural circuits, to make it easier to decode the more cryptic version when encountered.
In this specific case, we could even get rid of the pattern match and use the function that deconstructs a Maybe value:
In[3]:
getQueueMember :: Int -> ExceptT Text QueueAccess Member
getQueueMember position =
lift (Queue.getAt position) >>= maybe
(throwError "That position holds no member.")
pure
This does the same thing except pawns off the pattern matching to the maybe
function.
This idiom also comes in more advanced flavours, e.g. where a small chunk of code is wrapped in runMaybeT
to get short-circuiting behaviour in the results from otherwise monadic calls. I intend to write a separate article on that idiom, though, because it will require more explanation.