We've seen how to conditionally evaluate programs with theif true then "this" else "that"
syntax.With pattern matching we can change thecontrol flowof our program based on a richer set of conditions. We'll explore how to pattern match onliteral values,add "guards" for more granular conditions for our pattern, and explore structural pattern matching based on data types.
Pattern matching on literals
In some circumstances you might see pattern matching being used as a concise chain of if / else statements.
foodUnit : Text -> Text
foodUnit f =
"👇 compare f with the following..."
match f with
"Pie" -> "slice"
"Coffee" -> "cup"
"Soup" -> "bowl"
"Pancake" -> "stack"
_ -> "???"
The first part of the pattern match syntaxmatch f with
identifies the target value that the pattern match will make its comparisons against. After thewith
keyword, Unison expects a series of arrow->
separated cases. In the example above, the valuef
will be compared against each value to the left of the arrow, starting from the top. The order in which the cases appear is important for the pattern match because if the value off
matches something on the left, Unison will evaluate the right side of the arrow.
In our example, we've provided a "catch-all" case at the end of the pattern match. It starts with an underscore:_ -> "fallback value"
🎨It's a common practice to provide a catch-all. The following code will currently typecheck, but willlikely be a type error in a future version of Unison:
mySoupCount : Text -> Nat
mySoupCount name =
"👇 no fallback value"
match name with
"Chicken Noodle" -> 4
"Miso" -> 7
"Borscht" -> 5
"Chowder" -> 5
mySoupCount "Gazpacho"
The error returned by the UCM reads:
💔💥
I've encountered a pattern match failure in function `mySoupCount` while scrutinizing:
"Gazpacho"
This happens when calling a function that doesn't handle all possible inputs
I'm sorry this message doesn't have more detail about the location of the failure. My makers plan
to fix this in a future release.
Variables in pattern matching
Let's say we want to save a value on the left-hand side of the case statements for use when evaluating the expression on the right-hand side. Let's do that below:
use Universal
magicNumber : Nat -> Text
magicNumber guess = match guess with
42 -> "magic 🪄"
n -> toText n ++ " is not the magic number. Guess again."
Heren
is a variable that will bind to any value of typeNat
.It's also functioning as a fallback value, and in the example above whatever value it has can be used to produce a desiredText
result.
Guard patterns
Guard patterns are a way we can build further conditions for the match expression. They can reference variables in the case statement to further refine if the right side of the arrow should be run. A guard pattern is started with pipe character|
where the right of the|
needs to return something of typeBoolean
.If the value to the right side of the pipe|
istrue
,the block on the right of the arrow will be executed.
use Universal
use Text
matchNum : Nat -> Text
matchNum num = match a with
oneTwo | (oneTwo === 1) || (oneTwo === 2) -> "one or two"
threeFour | (threeFour === 3) || (threeFour === 4) -> "three or four"
fiveSix | (fiveSix === 5) || (fiveSix === 6) -> "five or six "
_ -> "no match"
We've combined variables with guard patterns and boolean operators in our pattern match statement. The variableoneTwo
without the guard pattern would match anything of typeNat
,but we can specify in the pattern guard that the variableoneTwo
should either be theliteralvalue1
or2
.
You might see multiple guard patterns being used for the same match expression case. For example:
myMatch : Nat -> Text
myMatch num = match num with
n
| n < 3 -> "small number"
| n > 100 -> "big number"
| otherwise -> "medium number"
Each pipe|
is followed by a boolean expression and an expression to execute if the boolean istrue
.The last case| otherwise ->
in this syntax is a pass-through to catch all other cases. In the context of this pattern match form,otherwise
simply meanstrue
.
Cases syntax
When writing a pattern match where the last value or set of values are being compared against successive cases, or "scrutinized", you can use thecases
shorthand rather than writing out the full match statement.
So a match expression with one argument might look like this when fully written out:
myMatch : Nat -> Text
myMatch nat = match nat with
n | n < 3 -> "small number"
_ -> "big number"
But it can also be expressed as:
myMatch : Nat -> Text
myMatch = cases
n | n < 3 -> "small number"
_ -> "big number"
At first glance it might appear that there is no argument to themyMatch
function, but when we see thecases
keyword, we know that there is at least one inferred argument being tested against the cases in our match expression.
Two or more arguments can also be scrutinized with thecases
syntax, but when multiple arguments are being tested, they arecomma separatedin the case statements.
twoCases : Nat -> Nat -> Text
twoCases = cases
n1, n2 | n1 === n2 -> "same value"
_, _ -> "different values"