Logic Operators

Comparison Operators

OperatorDescription
(x operator y)
x, y same type or are numericx, y different types
==x is equals to yerror if not definedfalse if not defined
!=x is not equals to yerror if not definedtrue if not defined
>x is greater than yerror if not definedfalse if not defined
>=x is greater than or equals to yerror if not definedfalse if not defined
<x is less than yerror if not definedfalse if not defined
<=x is less than or equals to yerror if not definedfalse 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

Tip: Always the full set

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

Note

All boolean operators are built in for the bool data type.

OperatorDescriptionArityShort-circuits?
! (prefix)NOTunaryno
&&ANDbinaryyes
&ANDbinaryno
||ORbinaryyes
|ORbinaryno

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

OperatorDescriptionArityShort-circuits?
??Null-coalescebinaryyes

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 ()

Tip: Default value for object map property

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;
}