Logic Operators
Comparison Operators
Operator | Description ( x operator y ) | x , y same type or are numeric | x , y different types |
---|---|---|---|
== | x is equals to y | error if not defined | false if not defined |
!= | x is not equals to y | error if not defined | true if not defined |
> | x is greater than y | error if not defined | false if not defined |
>= | x is greater than or equals to y | error if not defined | false if not defined |
< | x is less than y | error if not defined | false if not defined |
<= | x is less than or equals to y | error if not defined | false if not defined |
Comparison operators between most values of the same type are built in for all standard types.
Others are defined in the LogicPackage
but excluded when using a raw Engine
.
Floating-point numbers interoperate with integers
Comparing a floating-point number (FLOAT
) with an integer is also supported.
42 == 42.0; // true
42.0 == 42; // true
42.0 > 42; // false
42 >= 42.0; // true
42.0 < 42; // false
Decimal numbers interoperate with integers
Comparing a Decimal
number with an integer is also supported.
let d = parse_decimal("42");
42 == d; // true
d == 42; // true
d > 42; // false
42 >= d; // true
d < 42; // false
Strings interoperate with characters
Comparing a string with a character is also supported, with the character first turned into a string before performing the comparison.
'x' == "x"; // true
"" < 'a'; // true
'x' > "hello"; // false
Comparing different types defaults to false
Comparing two values of different data types defaults to false
unless the appropriate operator
functions have been registered.
The exception is !=
(not equals) which defaults to true
. This is in line with intuition.
42 > "42"; // false: i64 cannot be compared with string
42 <= "42"; // false: i64 cannot be compared with string
let ts = new_ts(); // custom type
ts == 42; // false: different types cannot be compared
ts != 42; // true: different types cannot be compared
ts == ts; // error: '==' not defined for the custom type
Safety valve: Comparing different numeric types has no default
Beware that the above default does NOT apply to numeric values of different types
(e.g. comparison between i64
and u16
, i32
and f64
) – when multiple numeric types are
used it is too easy to mess up and for subtle errors to creep in.
// Assume variable 'x' = 42_u16, 'y' = 42_u16 (both types of u16)
x == y; // true: '==' operator for u16 is built-in
x == "hello"; // false: different non-numeric operand types default to false
x == 42; // error: ==(u16, i64) not defined, no default for numeric types
42 == y; // error: ==(i64, u16) not defined, no default for numeric types
Caution: Beware operators for custom types
It is strongly recommended that, when defining operators for custom types, always define the
full set of six operators together, or at least the ==
and !=
pair.
Operators are completely separate from each other. For example:
-
!=
does not equal!(==)
-
>
does not equal!(<=)
-
<=
does not equal<
plus==
-
<=
does not imply<
Therefore, if a custom type misses an operator definition, it simply raises an error or returns the default.
This behavior can be counter-intuitive.
let ts = new_ts(); // custom type with '<=' and '==' defined
ts <= ts; // true: '<=' defined
ts < ts; // error: '<' not defined, even though '<=' is
ts == ts; // true: '==' defined
ts != ts; // error: '!=' not defined, even though '==' is
Boolean Operators
All boolean operators are built in for the bool
data type.
Operator | Description | Arity | Short-circuits? |
---|---|---|---|
! (prefix) | NOT | unary | no |
&& | AND | binary | yes |
& | AND | binary | no |
|| | OR | binary | yes |
| | OR | binary | no |
Double boolean operators &&
and ||
short-circuit – meaning that the second operand will not be evaluated
if the first one already proves the condition wrong.
Single boolean operators &
and |
always evaluate both operands.
a() || b(); // b() is not evaluated if a() is true
a() && b(); // b() is not evaluated if a() is false
a() | b(); // both a() and b() are evaluated
a() & b(); // both a() and b() are evaluated
Null-Coalescing Operator
Operator | Description | Arity | Short-circuits? |
---|---|---|---|
?? | Null-coalesce | binary | yes |
The null-coalescing operator (??
) returns the first operand if it is not ()
, or the second
operand if the first operand is ()
.
It short-circuits – meaning that the second operand will not be evaluated if the first
operand is not ()
.
a ?? b // returns 'a' if it is not (), otherwise 'b'
a() ?? b(); // b() is only evaluated if a() is ()
Use the null-coalescing operator to implement default values for non-existent object map properties.
let map = #{ foo: 42 };
// Regular property access
let x = map.foo; // x == 42
// Non-existent property
let x = map.bar; // x == ()
// Default value for property
let x = map.bar ?? 42; // x == 42
Short-circuit loops and early returns
The following statements are allowed to follow the null-coalescing operator:
This means that you can use the null-coalescing operator to short-circuit loops and/or
early-return from functions when the value tested is ()
.
let total = 0;
for value in list {
// Whenever 'calculate' returns '()', the loop stops
total += calculate(value) ?? break;
}