Switch Expression
The switch
expression is a powerful control flow construct that allows pattern matching on its input.
It is constructed with the switch
keyword followed by an input expression enclosed in parenthesis ()
and a code block enclosed in curly braces {}
. Inside this code block we encounter the case
keyword once or several times depending on the input.
switch (condition) {
case (a) {};
};
The case
keyword is followed by a pattern and an expression in curly braces {}
. Pattern matching is performed on the input and the possible values of the input are bound to the names in the pattern. If the pattern matches, then the expression in the curly braces evaluates.
Lets switch on a variant as an input:
type Color = { #Black; #White; #Blue };
let color : Color = #Black;
var count = 0;
switch (color) {
case (#Black) { count += 1 };
case (#White) { count -= 1 };
case (#Blue) { count := 0 };
};
We defined a variant type Color
, declared a variable color
with that type and declared another mutable variable count
and set it to zero. We then used our variable color
as the input to our switch
expression.
After every case
keyword, we check for a possible value of our variant. When we have a match, we execute the code in the expression defined for that case.
In the example above, the color is #Black
so the count
variable will be incremented by one. The other cases will be skipped.
If all expressions after every case (pattern)
evaluate to a value of the same type, then like in the example of the if else
expression, we could assign the return value of the whole switch
expression to a variable or use it anywhere else an expression is expected.
In the example above, our switch
expression evaluates to ()
.
A little program
Lets combine some concepts we have learned so far. We will use a Result
, an Option
, a mutable variable, a function
and a switch
expression together:
type Result = {
#ok : Nat;
#err : Text;
};
type Balance = ?Nat;
var balance : Balance = null;
func getBalance(bal : ?Nat) : Result {
switch (bal) {
case (null) {
#err "No balance!";
};
case (?amount) {
#ok amount;
};
};
};
We started by defining a Result
type with two variants #ok
and #err
. Each variant has an associated type namely Nat
and Text
.
Then we define an Option
type called Balance
. It is an optional value of type ?Nat
.
We then declare a mutable variable called balance
and annotate it with the Balance
type.
And lastly, we define a function that takes one argument of type ?Nat
and returns a value of type Result
. The function uses a switch
expression to check the value of our variable balance
.
The switch
expression checks two cases:
- In the case the value of
balance
isnull
, it returns the#err
variant with an associated text. This is returned to the function body, which is then treated as the return value of the function. - In the case the value of
balance
is some optional value?amount
, it returns the#ok
variant with an associated valueamount
.
In both cases we used pattern matching to check the values. In the last case we defined a new name amount
to bind our value, in case we found some optional value of type ?Nat
. If so, then that value is now available through this new name.
Lets now call this function.
let amount : Result = getBalance(balance);
balance := ?10;
let amount2 : Result = getBalance(balance);
The first call will yield #err "No balance!"
telling us that the balance is null
. We then mutate the value of our balance to a new optional value ?10
. When we call the function again, we get #ok 10
.
Note, we didn't have to annotate the amount
variable with the Result
type, but it does make it more clear to the reader what the expected type is of the function.