https://entropicthoughts.com/non-obvious-haskell-idiom-guard-sequence Entropic Thoughts Non-Obvious Haskell Idiom: Guard-Sequence * Home * Archive * Tags * About * xkqr.org Non-Obvious Haskell Idiom: Guard-Sequence by kqr , scheduled 2025-08-05 Tags: * haskell 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: * guard condition *> action performs the side-effect object action if the condition is true, and otherwise fails. * guard condition $> value returns the value if the condition is true, otherwise returns a failure.1[ ]^1 Most often, this means Just value on success and Nothing on failure. But it could also be a plain value on success and an exception on failure. The compiler knows what to pick based on the type expected. * value <$ guard condition also returns the value if the condition is true, but with emphasis on the value rather than the condition. 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 In[1]: 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. In[2]: 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 In[3]: 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 In[4]: 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 In[5]: 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 In[6]: (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: In[7]: 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 In[8]: 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. Sidenotes ^1 Most often, this means Just value on success and Nothing on failure. But it could also be a plain value on success and an exception on failure. The compiler knows what to pick based on the type expected. If you liked this and want more you should buy me a coffee. That helps me turn my 110+ ideas backlog into articles. Shoutout to my amazing wife without whose support I would never make it past the first sentence. S = k log W. Comments? Send me an email. You can also subscribe for updates.