An expression is a sequence of operators and operands. This chapter defines the syntax, order of evaluation of operands and operators, and meaning of expressions.
Expressions are constructed from operands and operators. The operators of an expression indicate which operations to apply to the operands. Examples of operators include +
, -
, *
, and /
. Examples of operands include literals, fields, and expressions.
There are the following kinds of operators:
Unary operators take one operand and use prefix notation such as -x
.
Binary operators take two operands and use infix notation such as x + y
.
Ternary operator. Only one ternary operator, ?:
, exists; it takes three operands and uses infix notation (c? x: y
).
Query comprehensions.
The order of evaluation of operators in an expression is constrained by the precedence and associativity of the operators. Unless otherwise specified, the order of evaluation of operands is undefined.
A single syntactic operator may have different meanings depending on the type of its operands. That is, an operator may be overloaded. In this case, the meaning and the return type is determined by selecting the most specific super type for both operands for which a meaning and return type are specified in Chapter 3.
Precedence and associativity determine how operators and operands are grouped together. For example, the expression x + y * z
is evaluated as x + (y * z)
because the *
operator has higher precedence than +
. The following table summarizes all operators in order of precedence from highest to lowest.
Category | Operators |
---|---|
Primary |
|
Unary |
|
Multiplicity (unary postfix) |
|
Multiplicative |
|
Additive |
|
Shift |
|
Relational and type testing |
|
Equality |
|
Logical And (conjunction) |
|
Logical Or (disjunction) |
|
Null Coalescing |
|
Conditional |
|
Query Comprehension |
|
Where |
|
Select |
|
Bitwise And, Intersection |
|
Bitwise Exclusive Or |
|
Bitwise Or, Union |
|
When an operand occurs between two operators with the same precedence, the associativity of the operators controls the order in which the operations are performed. All binary operators are left associative, that is, operations are performed left to right. For example, x + y + z
is evaluated as (x + y) + z
.
Precedence and associativity can be controlled using parentheses. For example, x + y * z
first multiplies y
by z
and then adds the result to x
, but (x + y) * z
first adds x
and y
and then multiplies the result by z
.
A MemberAccessExpression takes an expression that resolves to a scope as the left operand and a symbol as the right operand. Evaluating the expression returns the value bound to the symbol in the scope.
MemberAccessExpression: PrimaryExpression . MemberNameMemberName: Identifier
A MemberAccessExpression consists of a PrimaryExpression, followed by a “.” token, followed by a member selector. Consider the following MemberAccessExpression:
{Name = "Bill", Age = 23}.Age
The member access operator looks up the symbol Age
in the scope defined by the instance:
{Name = "Bill", Age = 23}
A symbol lookup is the process whereby the meaning of a name in a context is determined. A symbol lookup may occur as part of evaluating a SimpleName
or a MemberAccess
in an expression.
M is a lexically scoped language. Scopes introduce symbols and may nest, and an inner scope may introduce a symbol that hides a symbol in an outer scope. Initially a symbol is resolved against the lexically innermost scope. If no matching symbol is found in the innermost scope, lookup proceeds in the containing scope. This process continues until the outermost scope is reached, which is always a module.
The following are examples of scopes:
An entity definition
A module
A field definition
The left side of a where expression
A query expression
A member lookup of a name N
in a type T
is processed as follows: The set of all accessible members named N
declared in T
and the base types of T
is constructed. If no members named N
exist and are accessible, then the lookup produces no match.
Field declarations override lexical scoping to prevent the type of a declaration binding to the declaration itself. The ascribed type of a field declaration must not be the declaration itself; however, the declaration may be used in a constraint. Consider the following example:
type A; type B { A : A; }
The lexically enclosing scope for the type ascription of the field declaration A is the entity declaration B. With no exception, the type ascription A would bind to the field declaration in a circular reference that is an error. The exception allows lexical lookup to skip the field declaration in this case.
A declaration may be used within a constraint on the ascribed type, as in the following example:
type Node { Label : Text; Parent : Node; } Nodes : Node* where item.Parent in Nodes;
The right operand of the in
clause stipulates that the Parent
field of a node must be within the collection being defined, Nodes
.
Entity types and collections use a common initialization syntax.
An InitializationExpression constructs a new instance of a collection or entity.
InitializationExpression: { ElementInitializersopt } { ElementInitializers , } ElementInitializer: LeadingDottedIdentifier TypeAscriptionopt = Expression LeadingDottedIdentifer TypeAscriptionopt InitializationExpression ElementInitializers: ElementInitializer ElementInitializers , ElementInitializer LeadingDottedIdentifier: DottedIdentifier . DottedIdentifier
The following example initializes the extent SmallNumbers
to the collection of values 1
, 2
, 3
, 4
:
SmallNumbers { 1, 2, 3, 4 }
The following example initializes three extents: Colors
, Makes
, and Cars
. Although there is no intrinsic enumeration type in the M language, the first two extents are used as enumerations.
Colors { Red = "Red", Blue = "Blue", Yellow = "Yellow" } Makes { Ford = "Ford", Chevy = "Chevrolet" } Cars { { Make = Makes.Ford, Color = Colors.Blue } { Make = Makes.Chevy, Color = Colors.Red } }
M provides labeled collections to construct instances that reference each other. Consider the following type Person
and extent People
:
type Person { Id : Integer32 = AutoNumber(); Name : Text; Age : Integer32; Spouse : Person; } where identity Id; People : Person* where item.Spouse in People;
The Spouse
field references another Person
. One way to initialize this structure is to explicitly assign the identity of each instance:
People { { Id = 0, Name = "Jack", Age = 23, Spouse = 1 }, { Id = 1, Name = "Jill", Age = 25, Spouse = 0 }, }
This assumes that the values 0
and 1
are not already used in the Person
extent and exposes unnecessary implementation details. M provides label values to initialize references without explicit manipulation of identity values. Consider the following example, which initializes the same preceding structure:
People { Jack { Name = "Jack", Age = 23, Spouse = Jill }, Jill { Name = "Jill", Age = 25, Spouse = Jack }, }
The label Jack
introduces an identifier that can be used to reference the instance. This allows the first instance, Jack
, to reference the second instance, Jill
, as a spouse and vice versa.
It is frequently useful to initialize a value in another structure. In the following example, computers have zero to many boards. This relationship is implemented as a reference from Board
to Computer
.
type Computer { Id : Integer32 = AutoNumber(); Processor : Text; } where identity Id; type Board { Id : Integer32 = AutoNumber(); Kind : Text; Computer : Computer; } where identity Id; Boards : Board* where item.Computer in Computers; Computers : Computer*;
Creating an instance of a computer requires initializing both the computer and the boards. This can be initialized “bottom up” as follows:
Computers { MyPC { Processor = "x86"} } Boards { { Kind = "Graphics", Computer = Computers.MyPC }, { Kind = "Sound", Computer = Computers.MyPC }, { Kind = "Network", Computer = Computers.MyPC }, }
Using non-local initialization, this same structure can be initialized “top down” as follows:
Computers { MyPC { Processor = "x86", .Boards { { Kind = "Graphics", Computer = MyPC }, { Kind = "Sound", Computer = MyPC }, { Kind = "Network", Computer = MyPC }, } } }
The dot prefix to the Boards label (.Boards
) does not introduce a new label into the current scope. Rather, it looks up the symbol at the extent scope and adds the content to that extent.
The Identifier in an InvocationExpression resolves to a computed value declaration of the same name and arity. Evaluating an invocation expression causes each argument to be evaluated. The result of each argument is bound to the formal parameter in the corresponding position. The result of evaluating the body of the computed value declaration is the value of the invocation expression:
InvocationExpression: Identifier InvocationExpressionArguments InvocationExpressionArguments: ( Argumentsopt ) Arguments: Argument Arguments , Argument Argument: Expression
The following rules define the grammar for primary expressions:
PrimaryExpression: | |
PrimaryCreationExpression | |
PrimaryCreationExpression: | |
Literal | |
SimpleName | |
ParenthesizedExpression | |
MemberAccessExpression | |
InvocationExpression | |
InitializationExpression | |
EntityTypeExpression | |
ContextVariable |
Literal is defined in Section 2.4.3. EntityTypeExpression is defined in Section 3.6. The remaining non-terminals are defined in this section.
A SimpleName consists of a single identifier.
SimpleName: | |
Identifier |
In the expression:
Person.Age
Both Person
and Age
are SimpleNames.
The following rules define the grammar for context variables:
ContextVariable
this
value
item
The context variable this
is defined in Section 3.2. value
and item
are defined in Section 5.16.1.
The following rules define the grammar for unary operators:
UnaryExpression: PrimaryExpression + PrimaryExpression - PrimaryExpression ! PrimaryExpression ~ PrimaryExpression PrimaryExpression # IdentityExpression UniqueExpression IdentityExpression: identity Identifier identity ( Identifiers ) UniqueExpression: unique Identifier unique ( Identifiers )
The identity constraint is discussed in Section 3.6.2.
The type rules for unary operators are defined in Number (Section 3.5.3.1), Logical (Section 3.5.6), Binary (Section 3.5.7), and Collection (Section 3.7.2).
Examples of unary operators follow:
+1 -2 !true ~0x00 {1,2,3}# identity Id unique Name
The following rules define the grammar for multiplicity operators:
MultiplicityExpression: UnaryExpression UnaryExpression ? UnaryExpression + UnaryExpression * UnaryExpression # IntegralRange IntegralRange: IntegerLiteral IntegerLiteral .. IntegerLiteral .. IntegerLiteral
The type rules for multiplicity operators are defined in type operators in Section 3.4.
Examples of multiplicity expressions follow:
Integer32? Text#2..4 {Name : Text; Age : Integer32}*
The following rules define the grammar for arithmetic operators:
AdditiveExpression: MultiplicativeExpression AdditiveExpression + MultiplicativeExpression AdditiveExpression - MultiplicativeExpression MultiplicativeExpression: UnaryExpression MultiplicativeExpression * MultiplicityExpression MultiplicativeExpression / MultiplicityExpression MultiplicativeExpression % MultiplicityExpression
The type rules on arithmetic operators are defined in Number (Section 3.5.3), Text (Section 3.5.4), Date (Section 3.5.9), and Time (Section 3.5.11).
Examples of arithmetic operators follow:
1 + 1 2 * 3 "Hello " + "World"
The following rules define the grammar for shift operators:
ShiftExpression: AdditiveExpression ShiftExpression << AdditiveExpression ShiftExpression >> AdditiveExpression
The type rules on shift operators are defined in Binary, Section 3.5.7.
The following rules define the grammar for relational and type testing operators:
RelationalExpression: ShiftExpression RelationalExpression < ShiftExpression RelationalExpression > ShiftExpression RelationalExpression <= ShiftExpression RelationalExpression >= ShiftExpression RelationalExpression in ShiftExpression RelationalExpression : ShiftExpression
The type rules on relational and type-testing operators are throughout Chapter 3.
The following rules define the grammar for equality operators:
EqualityExpression: RelationalExpression EqualityExpression == RelationalExpression EqualityExpression != RelationalExpression
The type rules on equality operators are throughout Chapter 3.
The following rules define the grammar for logical operators.
LogicalAndExpression: EqualityExpression LogicalAndExpression && EqualityExpression LogicalOrExpression: LogicalAndExpression LogicalOrExpression || LogicalAndExpression
The type rules on logical operators are defined in Logical, Section 3.5.6.
There are two conditional operators: coalesce and conditional.
The ??
operator is called the null coalescing operator:
NullCoalescingExpression: LogicalOrExpression LogicalOrExpression ?? NullCoalescingExpression
A null coalescing expression of the form a ?? b
requires a
to be nullable. If a
is not null, the result of a ?? b
is a
; otherwise, the result is b
. The operation evaluates b
only if a
is null.
b
must be of the same type as a
without the value null.
The ?:
operator is called the conditional operator. It is at times also called the ternary operator.
ConditionalExpression: NullCoalescingExpression NullCoalescingExpression ? Expression : Expression
A conditional expression of the form b ? x : y
first evaluates the condition b
. Then, if b
is true
, x
is evaluated and becomes the result of the operation. Otherwise, y
is evaluated and becomes the result of the operation. A conditional expression never evaluates both x
and y
.
The conditional operator is right-associative, meaning that operations are grouped from right to left. For example, an expression of form a ? b : c ? d : e
is evaluated as a ? b : (c ? d : e)
.
The first operand of the ?:
operator must be an expression of a type that can be implicitly converted to Logical
; otherwise a compile-time error occurs. The middle and left operands must be of compatible types. The result of the conditional is the least specific type.
Query expressions provide a language integrated syntax for queries that is similar to relational and hierarchical query languages such as Transact SQL and XQuery.
A query expression begins with a from
clause and ends with either a select
, group
, or accumulate
clause. The initial from
clause can be followed by zero or more from
, let
, or where
clauses. Each from
clause is a scope that introduces one or more iteration identifiers ranging over a sequence or a join of multiple sequences. Each let
clause computes a value and introduces an identifier representing that value, and each where
clause is a filter that excludes items from the result that do not satisfy the Logical
expression. The final select
, accumulate
, or group
clause specifies the shape of the result in terms of the iteration identifiers(s). Finally, an into
clause can be used to “splice” queries by treating the results of one query as a generator in a subsequent query:
QueryExpression: ConditionalExpression QueryFromClause QueryBody QueryBody: QueryBodyClausesopt QueryConstructor QueryContinuationopt QueryBodyClauses: QueryBodyClause QueryBodyClauses QueryBodyClause QueryBodyClause: QueryFromClause QueryLetClause QueryWhereClause QueryJoinClause QueryJoinIntoClause QueryConstructor: QuerySelectClause QueryGroupClause QueryAccumulateClause QueryContinuation: intoIdentifier QueryBody QueryFromClause: from Identifier in ConditionalExpression QueryLetClause: let Identifier = ConditionalExpression QueryJoinClause: join Identifier in Expression on Expression equals ConditionalExpression QueryJoinIntoClause: join Identifier in Expression on Expression equals Expression into Identifier QueryWhereClause: where ConditionalExpression QuerySelectClause: select ConditionalExpression QueryGroupClause: group Expression by ConditionalExpression QueryAccumulateClause: QueryLetClause accumulate ConditionalExpression
The accumulate keyword generalizes Sum, Min, Max, et cetera. Its purpose is to repeatedly apply an expression to each element in a collection and accumulate the result. Consider the following fragment:
from c in CollectionExpression let a = Expression accumulate Expression
As an example, the following M code sums the elements in the collection Numbers:
from n in Numbers let i = 0 accumulate i + n
There are two compact forms for query expressions the binary infix where
and select
.
The infix where
operation filters elements from a collection that match a predicate:
WhereExpression: QueryExpression QueryExpression where WhereExpressions WhereExpressions: WhereExpression WhereExpressions , WhereExpression
The WhereExpression introduces the identifier value
into the scope of the right side to refer to an element of the collection on the left. The right side may also use any other identifiers that are in lexical scope.
If the QueryExpression returns a collection of collections (e.g. Number*
); then the right side also introduces the identifier item
into scope to refer to elements of the base domain.
The following example uses value
to filter the Numbers collection:
OneToTen : Number where value > 0 && value <= 10;
When used over a collection type, value refers to the collection:
SmallCollection : Number* where value.Count == 2; SmallCollectionOneToTen : (Number where value > 0 && value <=10)* where value.Count < 10;
The keyword item constrains the values of elements of elements. The following declaration is equivalent to the previous:
SmallCollectionOneToTen : Number* where value.Count < 10 && item > 0 && item <= 10;
Formalizing this convention:
QueryExpression where Expression
is a compact syntax for one of the two following expressions:
from value in QueryExpression where Expression select value from value in QueryExpression where (from item in value select Expression).All select value
The choice between the first and second expansions is made based on the type of the left operand to the where
clause. If it is a collection of collections the second expansion is used; otherwise, if it is a collection, the first expansion is used; otherwise it is a type error.
The select operator applies an expression to every element in a collection and returns the results in a new collection:
SelectExpression: WhereExpression WhereExpression select Expression
The SelectExpression introduces the identifier value into the scope of the right side to refer to an element of the collection on the left. The right side may also use any other identifiers that are in lexical scope.
Examples of the select operator follow:
{1, 2, 3} select value * 2 People select value.Name {{}, {1}, {1,1}} select value#
The following rules define the grammar for binary and collection operators:
InclusiveOrExpression: ExclusiveOrExpression InclusiveOrExpression | ExclusiveOrExpression ExclusiveOrExpression: AndExpression ExclusiveOrExpression ^ AndExpression AndExpression: SelectExpression AndExpression & SelectExpression
The type rules on binary and collection operators are defined in Binary, Section 3.5.7, and Collection, Section 3.7.2.