2.0 KiB
Single Functions Defining/Pattern Matching may have 1 or more guard clauses.
[!info] case .. of expressions can also use guard clauses.
These allow functions to switch on boolean expressions based on the inputs, and can often take up less space than explicit Expressions/if .. then .. else .. expressions.
Guard patterns are placed after the function arguments and take the form | <Boolean expr> = <body>
. There can be any number of guard patterns, as long as all cases are exhaustively covered.
Example
Strings can't be structurally pattern matched, so in order to ask if a string starts with a substring we need to call a function like Data.String.Utils.startsWith
.
We could do this with if then else
:
ensureLeadingSlash :: String -> String
ensureLeadingSlash str =
if String.Util.startsWith "/" then
str
else
"/" <> str
Alternatively, we could implement this with guard patterns:
ensureLeadingSlash :: String -> String
ensureLeadingSlash str
| String.Util.startsWith "/" str = str
| otherwise = "/" <> str
When the first pattern String.Util.startsWith "/" str
returns true
, the function will use ... = str
. Otherwise, it will prepend /
to str
.
Tip
otherwise
is simply an alias fortrue
, specifically for better-reading fallthrough guard patterns.
Much less often, but occasionally useful is the ability to structurally pattern match in a guard clause:
ensureLeadingSlashM :: Maybe String -> String
ensureLeadingSlashM mstr
| Just s <- mstr, String.Util.startsWith "/" s = s
| otherwise = ""
Here the bit Just s <- mstr
is saying "when mstr
is Just s
..."
then String.Util.startsWith "/" s
says "and s
starts with "/"
..."
| Just s <- mstr, startsWith "/" s = s
"when mstr
is Just s
and s
starts with "/"
, return s"
| otherwise = ""
otherwise return ""
You can have any number of comma-separated guard bindings before the last boolean expression.