Conditionals
Pint has three different ways to express conditionals: select expressions, cond
expressions, and
if
statements. Conditionals allow you to "branch" your code depending on some condition(s). The
word "branch" is between quotations because Pint is not an imperative language and does not
"execute" code. Therefore, conditionals are simply a way of saying: "choose between some expressions
or some constraints based on some condition(s)".
Pint also has match
statements that are useful to reason about unions. Both unions and match
will be discussed separately in Chapter 3.5
Select Expressions
A select expression allows you to select between two alternatives based on a condition. The two alternatives must have the same type and that type determines the type of the whole select expression. For example:
let y = (number < 5 ? 1 : 2);
All select expressions start with a condition followed by the ?
symbol. In this case, the
condition checks whether or not the local variable number
has a value less than 5. We place the
expression that should be chosen if the condition is true
immediately after the ?
symbol. The
symbol :
is then added followed by the expression that should be chosen if the condition is
false
. Both options, 1
and 2
, have the same type which is int
and so, the type of y
must
also be int
.
If, for example, the types of the two expressions we're selecting from do not match, the compiler will emit a compile error. For example, if we try to compile the following code:
predicate test(number: int) {
let y = number < 5 ? 1 : true;
}
we will get the following error:
Error: branches of a select expression must have the same type
╭─[test.pnt:2:13]
│
2 │ let y = number < 5 ? 1 : true;
│ ┬ ──┬─
│ ╰───────── 'then' branch has the type `int`
│ │
│ ╰─── 'else' branch has the type `bool`
───╯
The condition of a select expression must be a bool
. Otherwise, we will get a compile error. For
example, if we try to compile the following code:
predicate test(number: int) {
let y = number ? 1 : 2;
}
we will get the following error:
Error: condition for select expression must be a `bool`
╭─[test.pnt:2:13]
│
2 │ let y = number ? 1 : 2;
│ ───┬──
│ ╰──── invalid type `int`, expecting `bool`
───╯
Note that Pint will not automatically try to convert non-Boolean types to a Boolean. You must be
explicit and always provide a select expression with a Boolean
as its condition.
cond
Expressions
Pint provides cond
expressions. cond
expressions are generalized select expressions that are not
limited to only two branches. They provide selection from multiple alternatives, each based on some
condition. For example:
let z = cond {
x == 0 => 0,
x > 0 && x <= 10 => 1,
x > 10 && x <= 100 => 2,
else => 3
};
All cond
expressions start with the keyword cond
, followed by a comma-separated list of
statements in between curly brackets. Each statement describes a condition and an expression that
should be returned by the cond
if that condition is correct. The branches are evaluated in order
and the first one to become active determines the value of the cond
expression. If all branches
fail, then the cond
expression takes the value of the expression in the else
branch, which must
always be the last branch.
in the example above, z
is equal to 0
if x == 0
, equal to 1
if x
is between 0
and 10
,
equal to 2
if x
is between 10
and 100
, and equal to 3
otherwise.
every cond
expression can be rewritten using one or more select expressions. however, cond
tends
to be more compact and more readable than nested select expressions. for example, the cond
expression in the example above is equivalent to:
let z = x == 0 ? 0 :
x > 0 && x <= 10 ? 1
: x > 10 && x <= 100 ? 2 : 3;
similarly to select expressions, all candidate expressions must have the same type which determines
the type of the whole cond
expression. also, every condition must be a bool
or else a compile
error will be emitted.
if
Statements
if
statements are the last class of conditionals we will look at. Unlike select and cond
expressions, if
statements are not expressions, that is, they do not hold any values. Instead,
they allow predicating blocks of code based on some condition. A block of code in the context of
an if
statement is a collection of constraints and other if
statements. For example:
if number < 5 {
constraint x == y;
} else {
constraint x != y;
}
All if
statements start with the keyword if
, followed by a condition. In this case, the
condition checks whether or not the local variable number
has a value less than 5. The code block
that should be "active" if the condition is true
is placed immediate after the condition inside
curly brackets. Optionally, the keyword else
is then added followed by the code block that should
be active if the condition is false
(also between curly brackets). In the example above, the if
statement can be read as follows: "if number
is less than 5, then x
must be equal to y
.
Otherwise, x
must not be equal to y
".
Similarly to select expressions, the condition of an if
statement must be a bool
. Otherwise we
will get a compile error.
if
statements can be nested and can contain an arbitrary number of constraints:
if number < 5 {
constraint x == y;
if z > 0 {
constraint x > number;
constraint y > number;
}
} else {
constraint x != y;
if z > 0 {
constraint x > number;
constraint y > number;
} else {
constraint x < number;
constraint y < number;
}
}