214 lines
5.1 KiB
Markdown
214 lines
5.1 KiB
Markdown
# Control flow
|
|
|
|
## Blocks
|
|
|
|
Every block in Aiken is an expression. All expressions in the block are
|
|
executed, and the result of the last expression is returned.
|
|
|
|
```aiken
|
|
let value: Bool = {
|
|
"Hello"
|
|
42 + 12
|
|
False
|
|
} // False
|
|
```
|
|
|
|
Expression blocks can be used instead of parenthesis to change the precedence of operations.
|
|
|
|
```aiken
|
|
let celsius = { fahrenheit - 32 } * 5 / 9
|
|
```
|
|
|
|
## Matching
|
|
|
|
The `when *expr* is` expression is the most common kind of flow control in Aiken code. It
|
|
allows us to say "if the data has this shape then do that", which we call
|
|
_pattern matching_.
|
|
|
|
Here we match on an `Int` and return a specific string for the values 0, 1,
|
|
and 2. The final pattern `n` matches any other value that did not match any of
|
|
the previous patterns.
|
|
|
|
```aiken
|
|
when some_number is {
|
|
0 -> "Zero"
|
|
1 -> "One"
|
|
2 -> "Two"
|
|
n -> "Some other number" // This matches anything
|
|
}
|
|
```
|
|
|
|
Aiken's `when *expr* is` is an expression, meaning it returns a value and can be used
|
|
anywhere we would use a value. For example, we can name the value of a when
|
|
expression with a `let` binding.
|
|
|
|
```aiken
|
|
type Answer {
|
|
Yes
|
|
No
|
|
}
|
|
|
|
let answer = Yes
|
|
|
|
let description =
|
|
when answer is {
|
|
Yes -> "It's yes!"
|
|
No -> "It's not yes."
|
|
}
|
|
|
|
description // => "It's true!"
|
|
```
|
|
|
|
## If-Else
|
|
|
|
Pattern matching on a `Bool` value is discouraged and `if / else`
|
|
expressions should be use instead.
|
|
|
|
```aiken
|
|
let some_bool = True
|
|
|
|
if some_bool {
|
|
"It's true!"
|
|
else {
|
|
"It's not true."
|
|
}
|
|
```
|
|
|
|
Note that, while it may look like an imperative instruction: if this then do
|
|
that or else do that, it is in fact one single expression. This means, in
|
|
particular, that the return types of both branches have to match.
|
|
|
|
## Destructuring
|
|
|
|
A `when *expr* is` expression can be used to destructure values that
|
|
contain other values, such as tuples and lists.
|
|
|
|
```aiken
|
|
when xs is {
|
|
[] -> "This list is empty"
|
|
[a] -> "This list has 1 element"
|
|
[a, b] -> "This list has 2 elements"
|
|
_other -> "This list has more than 2 elements"
|
|
}
|
|
```
|
|
|
|
It's not just the top level data structure that can be pattern matched,
|
|
contained values can also be matched. This gives `when` the ability to
|
|
concisely express flow control that might be verbose without pattern matching.
|
|
|
|
```aiken
|
|
when xs is {
|
|
[[]] -> "The only element is an empty list"
|
|
[[], ..] -> "The 1st element is an empty list"
|
|
[[4], ..] -> "The 1st element is a list of the number 4"
|
|
other -> "Something else"
|
|
}
|
|
```
|
|
|
|
Pattern matching also works in `let` bindings, though patterns that do not
|
|
match all instances of that type may result in a runtime error.
|
|
|
|
```aiken
|
|
let [a] = [1] // a is 1
|
|
let [b] = [1, 2] // Runtime error! The pattern has 1 element but the value has 2
|
|
```
|
|
|
|
## Assigning names to sub-patterns
|
|
|
|
Sometimes when pattern matching we want to assign a name to a value while
|
|
specifying its shape at the same time. We can do this using the `as` keyword.
|
|
|
|
```aiken
|
|
when xs is {
|
|
[[_, ..] as inner_list] -> inner_list
|
|
_other -> []
|
|
}
|
|
```
|
|
|
|
## Checking equality and ordering in patterns
|
|
|
|
The `if` keyword can be used to add a guard expression to a when clause. Both
|
|
the patterns have to match and the guard has to evaluate to `True` for the
|
|
clause to match. The guard expression can check for equality or ordering for
|
|
`Int`.
|
|
|
|
```aiken
|
|
when xs is {
|
|
[a, b, c] if a == b && b != c -> "ok"
|
|
_other -> "ko"
|
|
}
|
|
```
|
|
|
|
## Alternative clause patterns
|
|
|
|
Alternative patterns can be given for a when clause using the `|` operator. If
|
|
any of the patterns match then the clause matches.
|
|
|
|
Here the first clause will match if the variable `number` holds 2, 4, 6 or 8.
|
|
|
|
```aiken
|
|
when number is {
|
|
2 | 4 | 6 | 8 -> "This is an even number"
|
|
1 | 3 | 5 | 7 -> "This is an odd number"
|
|
_ -> "I'm not sure"
|
|
}
|
|
```
|
|
|
|
If the patterns declare variables then the same variables must be declared in
|
|
all patterns, and the variables must have the same type in all the patterns.
|
|
|
|
```aiken
|
|
when list is {
|
|
[1, x] | x -> x // Error! Int != List(Int)
|
|
_ -> 0
|
|
}
|
|
```
|
|
|
|
## Todo
|
|
|
|
Aiken's `todo` keyword is used to indicate that some code is not yet finished.
|
|
|
|
It can be useful when designing a module, type checking functions and types but
|
|
leaving the implementation of the functions until later.
|
|
|
|
```aiken
|
|
fn not_sure_yet() -> Int {
|
|
// The type annotations says this returns an Int, but we don't need
|
|
// to implement it yet.
|
|
todo
|
|
}
|
|
|
|
fn idk() {
|
|
favourite_number() * 2
|
|
}
|
|
```
|
|
|
|
When this code is built Aiken will type check and compile the code to ensure it
|
|
is valid, and the `todo` will be replaced with code that crashes the program if
|
|
that function is run.
|
|
|
|
A message can be given as a form of documentation. The message will be traced
|
|
when the `todo` code is run.
|
|
|
|
```aiken
|
|
fn not_sure_yet() -> Int {
|
|
todo("Believe in the you that believes in yourself!")
|
|
}
|
|
```
|
|
|
|
When the compiler finds a `todo` it will print a warning, which can be useful
|
|
to avoid accidentally forgetting to remove a `todo`.
|
|
|
|
The warning also includes the expected type of the expression that needs to
|
|
replace the `todo`. This can be a useful way of asking the compiler what type
|
|
is needed if you are ever unsure.
|
|
|
|
```aiken
|
|
fn foo() {
|
|
my_complicated_function(
|
|
// What type does this function take again...?
|
|
todo
|
|
)
|
|
}
|
|
```
|