Guards
Guards are special constructs used to catch NIL values in expressions. There are two guards, the ? and the | guard. The ? guard has high lexicographic precedence and is used with variables or parenthesized expressions:
X = A? + 1. // Terminate the transaction if A is NIL, otherwise add 1 and assign to X.
Y = (B A)?. // Assign the result of (B A) to Y if it is not NIL or terminate with an error otherwise.
The | guard is used to prevent delayed errors in expressions using reductions. For example, if F is a collection of collections, you can subscript F twice, but you need to use a guard if you are not sure that the corresponding element exists:
C = (1->("A"->"B"), 2->("C"->"D")).
X = C 3 | "A". // Terminates with an error if (C 3) does not exist
The
|guard is technically not necessary because the error is produced anyway due toNILnot being subscriptable. However, using the guard is a good practice because it preventsNILfrom leaking to downstream code causing hard-to-find bugs.
Guards as Casts
The ? and | guards are also casts that cast a nullable type into a non-nullable type, converting type X+ to a type X after required runtime checks.
X is int+ = NIL.
// Y is int = X. // will cause a compile error because X is int+, which is not compatible with int
Y is int = X?. // valid but produces runtime error if X is NIL
Declarative Cast
Declarative cast uses the keyword as and simply declares to the MUFL compiler that some value is compatible with a given type. For example, when assigning a string to a value list, it is sometimes important to declare that the value is compatible:
metadef theEnum:<$a,$b,$c>.
A is theEnum+ = NIL.
B is str = "a".
// invalid: A->B.
A -> B as(theEnum). // This is valid, but BE CAREFUL!
safe Cast
A safe cast generates runtime code to check the validity of the value:
metadef theEnum:<$a,$b,$c>.
A is theEnum+ = NIL.
B is str = "WRONG".
A -> B safe(theEnum). // Does not cause a compile error, but fails at runtime because B is not compatible with A
The err_ignore Expression
err_ignore { <expression> } evaluates its expression and returns the
result, but if the expression raises a runtime error it is suppressed and
the whole expression yields NIL instead of aborting. It is a lightweight
alternative to a provisional-abort handler for the common case of “try
this, and treat failure as NIL”:
A is int+ = NIL.
X = err_ignore { (A? + 1) }. // A? raises at runtime because A is NIL; X becomes NIL
The Else-Chain Expression
An else-chain ( <a> else <b> else <c> ... ) evaluates its operands left
to right and returns the first one that is not NIL, falling through to
the next on NIL. The operands must be type-compatible.
A is int+ = NIL.
_print (A else 5). // prints 5, because A is NIL