Entropic Thoughts

Non-Obvious Haskell Idiom: Guard-Sequence

Non-Obvious Haskell Idiom: Guard-Sequence

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. This is one of those examples, where we optionally return something with guard-sequence.

The guard–sequence idiom comes in three common shapes:

Explanation

Sometimes we have a boolean check that decides whether the return value is a failure or success. Some people create a function called ensure to do this, and it might be defined as

ensure :: Bool -> a -> Maybe a
ensure p x =
  if p then
    Just x
  else
    Nothing

Here is an example of how it could be used to check if a person is of age, and if so, return a ticket to an event for them. If they are a minor, it returns Nothing.

ensure (age >= 18) (ticket_for person)

I used to be surprised that the ensure function did not exist in the standard libraries, but there’s a good reason for this: we can phrase the same thing using combinations of existing operators. The guard function (available in Control.Monad) combined with the functor-replace $> operator (from Data.Functor) allows us to write

guard (age >= 18) $> ticket_for person

This is a tiny bit longer to write, but uses only standard functions and operators, which means it is more likely to be readable by someone else without having to first look up what ensure means in this specific context.

The way it works is that

  1. guard returns Nothing if the condition is not met, and Just () if the condition is met; then
  2. the $> operator is a no-op on Nothing values, but replaces whatever is inside a Just value on the left with the value on the right.

This means in this case it acts a little like the && operator in JavaScript. The JavaScript equivalent would be

age >= 18 && ticket_for(person)

The difference is, of course, that the Haskell version is more principled and doesn’t come with the confusing edge cases the JavaScript version does.

The Haskell version is also more clear in the presence of multiple conditions. We might write

guard (age >= 18 && age <= 24 || age >= 65)
  $> ticket_discount

where it is clear that the logical operators && and || work on the condition, whereas the rest is about translating the boolean to failure or success. In JavaScript, this code would be

(age >= 18 && age <= 24 || age >= 65) && ticket_discount

where the && operator is used both to combine conditions and short-circuit to handle failure.

The Haskell version can also be flipped around to indicate emphasis on the discount rather than the condition:

ticket_discount <$ guard (age >= 18 && age <= 24 || age >= 65)

Here, the <$ guard combination reads as a sort of if.

Another reason the standard phrasing is superior to an ensure function is that it is more flexible. In these cases, we have returned a constant pure value, but if we switch the functor-replace operator $> with the applicative sequence operator *> the right-hand side can be something effectful instead, like a parser. For example, a Swedish driver’s licence has a letter combination indicating what types of vehicles a person is allowed to drive. Maybe we have records where this field is completely absent for minors. We can then conditionally parse that field based on age, with something like

guard (age >= 18) *> munch1 isLetter

This parser will fail for any age less than 18 (regardless of whether or not they have a licence), and parse the type of licence in all other cases (failing as usual if none exists).

In this case, the return value is no longer a Maybe String but a Parser String, whose parsing automatically fails on underage records. This is because the guard and sequence operators are generic and work with any type that supports some sort of failure, not just Maybe.