Entropic Thoughts

Non-Obvious Haskell Idiom: Conditional For

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.