Non-Obvious Haskell Idiom: Conditional For
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. Now we want to execute a side effect only when a value exists. We will use the conditional for.
The conditional for idiom has the shape for_ value $ action
. If the value
exists, it will be given as an argument to the side-effectful action. If the
value does not exist, this statement is a no-op.
If the side-effectful action is a complicated multi-statement block, the action
is probably an anonymous function with a do
block:
for_ value $ \actual -> do intermediary <- actionUsing actual moreActions actual intermediary ...
Explanation
The most recent case in which I encountered this idiom was code that added an http response header only if there is a value for it.
The addHeader
function takes two arguments: the name of the header to set, and
the value to set the header to. In this case, the header we wanted to set was
the Access-Control-Allow-Origin header, to the value in the corsOrigin
variable. However, the corsOrigin
variable is not of type Text
, but Maybe
Text
– some pages do not need a cors header.
We don’t want to default to an empty header when no value exists. Rather, we want the header to not be added at all. This is how we can accomplish that:
for_ corsOrigin $ addHeader "Access-Control-Allow-Origin"
The for_
function, when given a Maybe something
value as its first argument,
executes the side effects of its second argument with the value contained inside
the first argument if it exists.
If we wanted to define this function for ourselves, we might have come up with
for_maybe maybeValue action = case maybeValue of Nothing -> pure () Just found -> action found
or, if our Haskell-fu is more advanced,
for_maybe maybeValue action = maybe (pure ()) action maybeValue
or, if we really hate our colleagues,
for_maybe = flip . maybe $ pure ()
The actual for_
function is different from this one, though, in that it also
works on container types other than Maybe a
. Imagine we want to set multiple
headers, e.g. because we want to set multiple cookies. If cookieData
is a
list of cookie data, we can write the code as
for_ cookieData $ addHeader "Set-Cookie" . formatCookie
This will loop through all elements in the cookie data list1 Which implies that it is a no-op if the list is empty. and for each of them, first format the cookie data into a cookie string, and then add a cookie header. It adds as many headers as there are list elements.
This is the same function we used for performing an action conditionally! If we
squint a little with our brains, we might see why: we can think of Maybe a
as
a collection type that can hold at most one value. The for_
function loops
through all elements of a collection and executes a side effect for each. When
given a value wrapped in a Maybe
type, it will either loop zero times (if the
value does not exist) or one time (if the value exists.) Thus, execution of the
side effect is conditional on whether the value exists.
But we can also ignore that correspondence and think of for_
as executing a
side effect conditionally on its argument existing.