primary ::= atom | array_literal_expr | apply_expr | field_reference | "(" expression ")" expression ::= primary | field_replace expression_list ::= expression ("," expression)*
An array literal expression allows an array value to be created and initialised with a single expression. The value of an array literal does not need to be constant, as it may contain nested expressions which are evaluated at runtime.
Just as a character literal forms an integer expression, so does a string literal form an array of integers. Hence, a string literal is just special syntax for an array literal.
array_literal_expr ::= array_literal | string_literal array_literal ::= "[" [expression_list] "]"
An array literal consists of a list of zero or more sub-expressions. The sub-expressions may be of any type, but the types of all sub-expressions must unify, or the compiler MUST reject the program (with a type error). The type of the array literal expression is Array(a), where a is the unified type of all of the sub-expressions.
The dynamic semantics of an array literal expression are to construct an array value with as many elements as there are sub-expressions. The value of each element is the evaluation of its corresponding sub-expression.
A string literal consists of zero or more byte values. The type of a string literal expression is Array(Int). The evaluation of a string literal is an array value, with one Int element per character, with each element evaluating to the corresponding character’s byte value. Thus, "hello" is equivalent to ['h', 'e', 'l', 'l', 'o'], which is equivalent to [104, 101, 108, 108, 111].
Function application expressions allow arguments to be passed to functions, and functions to be evaluated:
apply_expr ::= primary "(" [expression_list] ["," "..."] ")"
Primarily, function application has the form f(v1, v2, ..., vn). This is known as full function application (due to the lack of a trailing “...” token). f must have type (t1, t2, ..., tn) -> t, or the expression is a type error, and the compiler MUST reject the program. Each value vi must have type ti, where 1 <= i <= n. This expression has type t.
The evaluation of this expression first causes f and each vi to be evaluated. This expression evaluates to the result of the full application of function f to all actual parameters v1 ... vn (see procedure evaluation). All side-effects involved in the function’s evaluation occur at the time of this expression’s evaluation.
Full function application expressions are the only kind of expression which may exhibit side-effects. The order in which these side-effects take place is undefined, except that all side-effects produced by one statement occur before its successor statement. Side-effects include input/output and destructive update of data structures.
An extended form f(v1, v2, ..., vm, "...") is known as partial function application (it has a trailing “...” token). f must have type (t1, t2, ..., tn) -> t, where m <= n, or the expression is a type error, and the compiler MUST reject the program. Each value vi must have type ti, where 1 <= i <= m. This expression has type (tm+1, tm+2, ..., tn) -> t.
The evaluation of this expression first causes f and each vi to be evaluated, in order from left to right (from v1 to vm). This expression evaluates to the result of the partial application of function f to all actual parameters v1 ... vm (see procedure evaluation).
A field reference retrieves the value of a named field of a user-defined type:
field_reference ::= primary "." lower_identifier
Recall that user-defined types consist of one or more constructors, each with zero or more parameters, each with an optional name. The same name may appear in multiple constructors, but must have the same type in each.
For a field reference of the form obj.field, obj MUST have a user-defined type, and MUST have at least one constructor with a parameter named field, or the compiler must reject the program. The type of the field reference is the type of the parameter named field in any constructor of the type of obj.
The evaluation of the expression causes obj to be evaluated first. If the constructor used to construct obj has a parameter named field, then the value of the field reference is the value of the parameter field. If the constructor used to construct obj does not have a parameter named field, then a runtime error occurs, terminating the program.
?> x = Cons(1, Nil) ?> x.head 1 ?> x.tail Nil ?> Nil.head Runtime Error: List instance has no field 'head'
Due to the possibility of runtime errors, it is recommended that field references be used only on types with a single constructor, or fields which appear in all constructors of a type. For other types, the more powerful switch statement should be used. The switch statement can also be used to access fields without names.
Field replace expressions¶
A field replace expression is used to non-destructively modify a named field of a user-defined type:
field_replace ::= primary "." lower_identifier ":=" expression
The := operator is right-associative, and has lower precedence than field references or function application. Hence, x.foo := y.bar := z is equivalent to x.foo := (y.bar := z). Updating multiple fields simultaneously therefore requires parentheses, for example, (x.foo := y).bar := z.
A field replace expression of the form obj.field := e produces a new value which is the same as obj but with field field replaced with the value of e. The original obj is not modified. The same compile-time and runtime rules as field reference expressions apply. The expression e MUST have the same type as the type of the parameter named field in any constructor of the type of obj.
The type of the field replace expression is the type of obj. The evaluation of the expression causes obj to be evaluated first. If the constructor used to construct obj has a parameter named field, then the value of the field replace is a value with the same constructor as obj, and all parameters the same as obj, except the parameter named field having the value of e. If the constructor used to construct obj does not have a parameter named field, then a runtime error occurs, terminating the program.
Note that obj is evaluated before e. If obj does not have an appropriate constructor, it is unspecified whether e is evaluated or not.
?> x = Cons(1, Nil) ?> x.head := 2 Cons(2, Nil) ?> x.tail := Cons(2, Nil) Cons(1, Cons(2, Nil)) ?> x Cons(1, Nil) ?> Nil.head := 1 Runtime Error: List instance has no field 'head'
As with any other expression, field replace expressions do not update the variable obj. Therefore, it is common to perform an “update” as follows:
?> x = x.head := 2 ?> x Cons(2, Nil)
Unlike conventional languages, this will not modify any aliases of x (it is literally an assignment of a new value to the variable named x). This can help avoid confusing bugs, and make your software more reliable overall (indeed, it is the reason behind Mars being a declarative language), but it can be confusing if you are used to conventional languages.
See also: Field update statements.