Chapter 6. Expressions

An expression produces a value when executed. An algorithm expressed using functions and expressions, rather than sequences of statements is often easier to understand and refactor. Therefore, Ceylon has a highly flexible expressions syntax. Expressions are formed from:

Ceylon expressions are validated for typesafety at compile time. To determine whether an expression is assignable to a program element such as a value or parameter, Ceylon considers the type of the expression (the type of the objects that are produced when the expression is evaluated). An expression is assignable to a program element if the type of the expression is assignable to the declared type of the program element.

Within a dynamic block, an expression may have no type, in the sense that its type can not be determined using static analysis of the code.

6.1. Literal values

Ceylon supports literal values of the following types:

  • Integer and Float,

  • Character, and

  • String.

The types Integer, Float, Character, and String are defined in the module ceylon.language.

Note: Ceylon does not need a special syntax for Boolean literal values, since Boolean is just a class with the cases true and false. Likewise, null is just the singleton value of an anonymous class.

Literal: IntegerLiteral | FloatLiteral | CharacterLiteral | StringLiteral | VerbatimStringLiteral

All literal values are instances of immutable types. The value of a literal expression is an instance of the type. How this instance is produced is not specified here.

6.1.1. Integer number literals

An integer literal, as defined in §2.4.1 Numeric literals, is an expression of type Integer, representing a numeric integer.

Integer five = 5;
Integer mask = $1111_0000;
Integer white = #FFFF;

6.1.2. Floating point number literals

A floating point literal, as defined in §2.4.1 Numeric literals, is an expression of type Float, a floating-point representation of a numeric value.

shared Float pi = 3.14159;

6.1.3. Character literals

A single character literal, as defined in §2.4.2 Character literals, is an expression of type Character, representing a single 32-bit Unicode character.

if (exists ch=string[i], ch == '+') { ... }

6.1.4. Character string literals

A character string literal or verbatim string, as defined in §2.4.3 String literals, is an expression of type String, representing a sequence of Unicode characters.

person.name = "Gavin King";
print("Melbourne\tVic\tAustralia\nAtlanta\tGA\tUSA\nGuanajuato\tGto\tMexico\n");
String verbatim = """A verbatim string can have \ or a " in it."""";

6.2. String templates

A character string template contains interpolated expressions, surrounded by character string fragments.

StringTemplate: StringStart (ValueExpression StringMid)* ValueExpression StringEnd

Each interpolated expression contained in the string template must have a type assignable to Object defined in ceylon.language.

print("Hello, ``person.firstName`` ``person.lastName``, the time is ``Time()``.");
print("1 + 1 = ``1 + 1``");

A string template is an expression of type String.

6.3. Self references and the current package reference

The type of the following expressions depends upon the context in which they appear.

SelfReference: "this" | "super" | "outer" | "package"

A self reference expression may not occur outside of a class or interface body.

The immediately containing class or interface for a program element is the class or interface in which the program element occurs, and which contains no other class or interface in which the program element occurs. If there is no such class or interface, the program element has no immediately containing class or interface.

A this, outer, or super self reference must have an immediately containing class or interface. An outer self reference must have an immediately containing class or interface for its immediately containing class or interface.

6.3.1. this

The keyword this refers to the current instance, as defined in §8.2.3 Current instance of a class or interface, of the immediately containing class or interface (the class or interface in which the expression appears). Its type is the type of the immediately containing class or interface.

6.3.2. outer

The keyword outer refers to the current instance, as defined in §8.2.3 Current instance of a class or interface, of the class or interface which immediately contains the immediately containing class or interface. Its type is assignable to the type of this class or interface.

6.3.3. super

The keyword super refers to the current instance of the immediately containing class or interface. Its type is the intersection of the immediate superclass and all immediate superinterfaces of the class. A member reference such as super.x may not resolve to a formal declaration, nor to any member inherited from more than one supertype of the intersection type.

The keyword super may occur as the first operand of an of operator, in which case the second operand is any supertype of the class. The expression (super of Type) has type Type. A member reference such as (super of Type).x may not resolve to a formal member, nor to any member inherited from more than one supertype of Type, nor to any member that is refined by the class or any intermediate supertype of the class.

6.3.4. package

The keyword package is not an expression, and does not have a well-defined type. However, it may be used to qualify and disambiguate a value reference or callable reference. A value reference or callable reference qualified by the keyword package always refers to a toplevel member of the containing package, never to an imported declaration or nested declaration.

6.4. Anonymous functions

An anonymous function is a function, as specified in §4.7 Functions, with no name, defined within an expression. It comprises one or more parameter lists, followed by an expression.

FunctionExpression: ("function" | "void")? Parameters+ (LazySpecifier | Block)

The parameters are the parameters of the function. The lazy specifier or block of code is the implementation of the function. If the void keyword is specified, the function is a void function. Otherwise, it is a non-void function, and its return type is inferred.

The type of an anonymous function expression is the callable type of the function, as specified in §4.7.1 Callable type of a function.

(Value x, Value y) => x<=>y
void (String name) => print(name)
(String string) {
    value mid = string.size % 2;
    return [string[0..mid],string[mid+1...]];
}

An anonymous function occurring in an extends clause may not contain a reference to a variable value.

Note: evaluation of an anonymous function expression, as defined in §8.4.5 Evaluation of anonymous functions results in instantiation of an object of type Callable. However, the members of this object are never in scope, do not hide other declarations, and are not referenceable from within the anonymous function.

Note: there is almost no semantic difference between the following function declarations:

Float f(Float x)(Float y) => x*y;
Float(Float) f(Float x) => (Float y) => x*y;

The first form is strongly preferred.

6.5. Compound expressions

An atom is a literal or self reference, a string template, an iterable or tuple enumeration, or a parenthesized expression.

Atom: Literal | StringTemplate | SelfReference | GroupedExpression | Iterable | Tuple | DynamicValue

A primary is formed by recursively forming member expressions and invocation expressions from an atom, base expression, or static expression.

Primary: Atom | BaseExpression | MemberExpression | StaticExpression | Invocation | Meta | Dec

More complex expressions are formed by combining expressions using operators, including assignment operators, and anonymous functions.

ValueExpression: Primary | OperatorExpression
Expression: ValueExpression | FunctionExpression | OperatorInvocation | OperatorMemberExpression

Parentheses are used for grouping:

GroupedExpression: "(" Expression ")"

A compound expression occurring in a dynamic block, and involving a qualified or unqualified reference with no type, or a reference to a declaration with no type, may also have no type.

In particular, if an operand expression has no type, and the type of the operator expression depends upon the type of the operand, and the operator expression occurs within a dynamic block, then the whole operator expression has no type.

6.5.1. Base expressions

A base expression is an unqualified identifier, with an optional list of type arguments:

BaseExpression: (MemberName | TypeName) TypeArguments?

A base expression is either:

  • a reference to a toplevel function, toplevel value, or toplevel class,

  • a reference within the lexical scope of the referenced function, value, or class, or

  • a reference within the body of the referenced function, value, or class.

The referenced declaration is determined by resolving the unqualified reference as defined by §5.1.7 Unqualified reference resolution. The unqualified realization for the unqualified reference is determined according to §3.7.6 Realizations.

The type argument list, if any, must conform, as defined by §3.6.1 Type arguments and type constraints, to the type parameter list of the unqualified realization.

If a base expression is a reference to an attribute, method, or member class of a class, the receiving instance is the current instance of that class, as defined by §8.2.3 Current instance of a class or interface. Otherwise, there is no receiving instance.

6.5.2. Member expressions

A member expression is a receiver expression, followed by an identifier, with an optional list of type arguments.

MemberExpression: (Primary ".") (MemberName | TypeName) TypeArguments?

A member expression is a reference to a member of a type: an attribute, method, or member class.

The referenced member is determined by resolving the qualified reference as defined by §5.1.8 Qualified reference resolution. The qualified realization for the qualified reference is determined according to §3.7.6 Realizations.

The type argument list, if any, must conform, as defined by §3.6.1 Type arguments and type constraints, to the type parameter list of the qualified realization.

The receiver expression produces the instance upon which the member is invoked or evaluated. When a member expression is executed, the receiver expresson is evaluated to produce the receiving instance which is held until the member is invoked or evaluated, as defined in §8.4 Evaluation, invocation, and assignment.

6.5.3. Value references

A value reference is a base expression or member expression that references a value declaration.

The type of a value reference expression is the type of the realization of the referenced value.

A value declaration is never generic, so a value reference never has a type argument list.

A value reference that does not occur within any dynamic block may not refer to a value declaration or value parameter with no type.

A value reference which occurs within a dynamic block and which does not reference any statically typed declaration, or which references a value declaration or value parameter with no type, has no type.

If a base expression or member expression does not reference any statically typed declaration, and occurs within a dynamic block, then it is considered a value reference.

6.5.4. Callable references

A callable reference is a base expression or member expression that references something—a function or class—that can be invoked or instantiated by specifying a list of arguments.

A callable reference may be invoked immediately, or it may be passed to other code which may invoke the reference. A callable reference captures the return type and parameter list types of the function or class it refers to, allowing compile-time validation of argument types when the callable reference is invoked.

The type of a callable reference expression is the callable type of the realization of the referenced function or class.

If a callable reference expression refers to a generic declaration, either:

  • it must be immediately followed by an argument list, allowing the compiler to infer the type arguments, or

  • it must have an explicit type argument list.

A callable reference may not appear as the receiver expression of a member expression.

Note: this restriction exists to eliminate an ambiguity in the interpretation of static expressions such as Person.string and Person.equals.

A callable reference that does not occur within any dynamic block may not refer to a function declaration with no return type.

A callable reference which occurs within a dynamic block and which references a function declaration with no return type, has no type.

Note: in a future release of the language, we would like to add a syntax for obtaining a callable reference to an attribute, something like person.@name, to allow attributes to be passed by reference. This would also allow static references like Person.@name.

6.5.5. Static expressions

A static expression is a type, followed by an identifier, with an optional list of type arguments.

StaticExpression: (TypeName TypeArguments? ".")+ (MemberName | TypeName) TypeArguments?

A static expression is a reference to a member of a type: an attribute, method, or member class.

The referenced member is determined by resolving the qualified reference as defined by §5.1.8 Qualified reference resolution. The qualified realization for the qualified reference is determined according to §3.7.6 Realizations.

The type argument list, if any, must conform, as defined by §3.6.1 Type arguments and type constraints, to the type parameter list of the qualified realization.

Unlike member expressions, a static expression does not have a receiver expression. All static expressions are callable expressions which accept an argument of the specified type.

A static expression must reference a statically typed declaration with no missing types, even within a dynamic block.

6.5.6. Static value references

A static value reference is a static expression that references an attribute declaration.

List<Anything>.size

The type of a static value reference expression for an attribute whose realization is of type X, and with qualifying type T, is X(T).

A value declaration is never generic, so a static value reference never ends in a type argument list.

6.5.7. Static callable references

A static callable reference is a static expression that references something—a method or member class—that can be invoked or instantiated.

List<String>.filter
Iterable<Integer>.map<String>

The type of a static callable reference expression for a method or member class whose realization has callable type C, and with qualifying type T, is C(T).

If a callable reference expression refers to a generic declaration, it must end in an explicit type argument list.

6.6. Invocation expressions

A callable expression—any expression of type Callable—is invokable. An invocation consists of an invoked expression, together with an argument list and, optionally, an explicit type argument list.

Invocation: Primary Arguments

The invoked expression must be of type Callable<R,P> for some types R and P. Then the type of the invocation expression is simply R.

If the invoked expression has no type, and occurs within a dynamic block, then the whole invocation expression has no type, and the argument list is not type-checked at compile time, unless it is a direct invocation expression.

An invocation expression must specify arguments for parameters of the callable object, either as a positional argument list, or as a named argument list.

Arguments: PositionalArguments | NamedArguments

Every argument list has a type, as specified below in §6.6.7 Positional argument lists and §6.6.8 Named argument lists. If an invocation is formed from a callable expression of type exactly Callable<R,P> and an argument list of type A, then A must be a subtype of P.

TODO: should we support an infix-operator-style syntax for method invocation like string split ",;".contains? This is especially nice for conceptually symmetric operations like a xor b, or when the argument is an anonymous function like people map (Person p)=>p.firstName+p.lastName.

6.6.1. Direct invocations

Any invocation expression where the invoked expression is a callable reference expression is called a direct invocation expression of the function or class to which the callable reference refers.

TODO: Should we consider x{y=1;}{z=2;} a legal direct invocation if x has multiple parameter lists?

In a direct invocation expression:

  • the compiler has one item of additional information about the schema of the method or class that is not reified by the Callable interface: the names of the parameters of the function or class, and therefore named arguments may be used, and

  • type argument inference is possible, as defined in §3.6.3 Type argument inference, since the compiler has access to the type parameters and constraints of the function or class.

If an invocation expression has a named argument list, it must be a direct invocation.

The type of a direct invocation expression is the return type of the realization of the function, or the type of the realization of the class, as defined in §3.7.6 Realizations.

If the function has no return type, and occurs within a dynamic block, then the whole direct invocation expression has no type.

In a direct invocation expression of a function or class, the restriction above on the argument list type is equivalent to the following requirements. Given the parameter list of the realization of the function or class, and the arguments of the direct invocation:

  • for each required parameter, an argument must be given,

  • for each defaulted parameter, an argument may optionally be given,

  • if the parameter list has a variadic parameter of type T+, one or more arguments must be given,

  • if the parameter list has a variadic parameter of type T*, one or more arguments may optionally be given,

  • no additional arguments may be given,

  • for a required or defaulted parameter of type T, the type of the corresponding argument expression must be assignable to T, and

  • for a variadic parameter of type T* or T+, the type of every corresponding argument expression must be assignable to T.

Furthermore, if type argument are inferred, then the inferred type arguments must conform, as defined by §3.6.1 Type arguments and type constraints, to the type parameter list of the realization of the function or class.

If an argument expression has no type, or if its parameter has no type, and the invocation occurs within a dynamic block, then the argument is not type-checked at compile time.

An invocation expression that does not occur within any dynamic block may not assign an argument to a value parameter with no type.

6.6.2. Default arguments

When no argument is assigned to a defaulted parameter by the caller, the default argument defined by the parameter declaration of the realization, as defined by §3.6.1 Type arguments and type constraints, of the function or class is used. The default argument expression is evaluated every time the method is invoked with no argument specified for the defaulted parameter.

This class:

shared class Counter(Integer initialCount=0) { ... }

May be instantiated using any of the following invocations:

Counter()
Counter(1)
Counter {}
Counter { initialCount=10; }

6.6.3. The type of a list of arguments

A list of arguments may be formed from:

  • any number of listed arguments, optionally followed by either

  • a spread argument, or

  • a comprehension.

ArgumentList: ((ListedArgument ",")* (ListedArgument | SpreadArgument | Comprehension))?

Every such list of arguments has a type, which captures the types of the individual arguments in the list. This type is always a subtype of Anything[]. The type of an empty list of arguments is [].

6.6.4. Listed arguments

A listed argument is an expression.

ListedArgument: Expression

If a listed argument is an expression of type T, and a list of arguments has type P with principal instantiation Sequential<Y>, then the type of a new argument list formed by prepending the expression to the first parameter list is Tuple<T|Y,T,P>.

6.6.5. Spread arguments

A spread argument is an expression prefixed by the spread operator *.

SpreadArgument: "* "Expression

The expression type T must have the principal instantiation {X*} for some type X. We form the sequential type of a spread argument as follows:

  • if the expression type T is an invariant subtype of X[], for some type X then the sequential type of the spread argument is T, or, if not,

  • if the expression type T is an invariant subtype of {X+}, for some type X then the sequential type of the spread argument is [X+], or, otherwise,

  • the expression type T is an invariant subtype of {X*}, for some type X and the sequential type of the spread argument is X[],

When a spread argument with an expression type not assignable to Anything[] is evaluated, the elements of the iterable automatically are packaged into a sequence.

Note: the spread "operator" is not truly an operator in the sense of §6.8 Operators, and so a spread argument is not an expresson. An expression, when evaluated, produces a single value. The spread operator produces multiple values. It is therefore more correct to view the spread operator as simply part of the syntax of an argument list.

The type of a list of arguments containing only a spread argument of sequential type S is simply S.

6.6.6. Comprehensions

A comprehension accepts one or more streams of values and produces a new stream of values. Any instance of Iterable is considered a stream of values. The comprehension has two or more clauses:

  • A for clause specifies a source stream and an iteration variable, as defined in §5.3.2 Iteration variables, representing the values produced by the stream.

  • An if clause specifies a condition list, as defined in §5.3.3 Control structure conditions, used to filter the values produced by the source stream or streams.

  • An expression clause produces the values of the resulting stream.

Every comprehension begins with a for clause, and ends with an expression clause. There may be any number of intervening for or if clauses. Each clause in the comprehension is considered a child of the clause that immediately precedes it.

Comprehension: ForComprehensionClause
ForComprehensionClause: "for" ForIterator ComprehensionClause
IfComprehensionClause: "if" ConditionList ComprehensionClause
ComprehensionClause: ForComprehensionClause | IfComprehensionClause | Expression

An expression that occurs in a child clause may refer to iteration variables and condition variables declared by parent clauses. The types of such variables are specified in §5.3 Control structures and assertions.

Note: each child clause can be viewed as a body nested inside the parent clause. The scoping rules for variables declared by comprehension clauses reflects this model.

The type of a list of arguments containing only a comprehension is [T*] where T is the type of the expression which terminates the comprehension, or [T+] if there are no if clauses, and if every for clause has an iterated expression of nonempty type.

An comprehension occurring in an extends clause may not contain a reference to a variable value.

Note: a comprehension, like a spread argument, is not considered an expression. An expression, when evaluated, produces a single value. A comprehension, produces multiple values, like a spread argument, or like a series of listed arguments. Therefore, a comprehension may only appear in an argument list or an enumeration expression. This is however, no limitation; we can simply wrap the comprehension in braces in order to get an expression of type {T*}, or in brackets to get an expression of type [T*].

TODO: properly define how expressions with no type occurring in a dynamic block affect comprehensions.

6.6.7. Positional argument lists

When invocation arguments are listed positionally, the argument list is enclosed in parentheses.

PositionalArguments: "(" ArgumentList ")"

The type of the positional argument list is the type of the list of arguments it contains.

6.6.8. Named argument lists

When invocation arguments are listed by name, the argument list is enclosed in braces.

NamedArguments: "{" NamedArgument* ArgumentList "}"

Named arguments may be listed in a different order to the corresponding parameters.

Each named argument in a named argument list is either:

  • an anonymous argument—an expression, with no parameter name explicitly specified,

  • a specified argument—a specification statement where name of the value of function being specified is interpreted as the name of a parameter, or

  • an inline getter, function, or anonymous class declaration, whose name is interpreted as the name of a parameter.

NamedArgument: AnonymousArgument | SpecifiedArgument | InlineDeclarationArgument

Additionally, a named argument list has an ordinary list of arguments, which may be empty. This argument list is interpreted as a single argument to a parameter of type Iterable.

{ initialCapacity=2; "hello", "world" }
{ initialCapacity=people.size; loadFactor=0.8; for (p in people) p.name->p }

Note: in a future release of the language, we would like to be able to assign a local name to an anonymous argument or listed argument, allowing it to be referenced later in the argument list. We might consider this a kind of "let" expression, perhaps.

Given a parameter list, and a named argument list, we may attempt to construct an equivalent positional argument list as follows:

  • Taking each argument in the named argument list in turn, on the order they occur lexically:

    • if the argument is anonymous, assign it to the first unassigned parameter of the parameter list, or

    • if the argument is named, assign it to the parameter with that name in the parameter list.

    If for any argument, there is no unassigned parameter, no parameter with the given name, or the parameter with the given name has already been assigned an argument, construction of the positional argument list fails, and the invocation is not well-typed.

  • Next, if the parameter list has an unassigned parameter of type exactly Iterable<T,N> for some types T and N, then an iterable enumeration expression, as defined in §6.6.12 Iterable and tuple enumeration, is formed from the ordinary list of arguments, and assigned to that parameter.

    If there is no such parameter, and the the ordinary list of arguments is nonempty, then construction of the positional argument list fails, and the invocation is not well-typed.

  • Finally, we assign each unassigned defaulted parameter its default argument.

The resulting equivalent positional argument list is formed by ordering the arguments according to the position of their corresponding parameters in the parameter list, and then replacing any inline value, function, or object declarations with a reference to the declaration.

The type of a named argument list is the type of the equivalent positional argument list.

6.6.9. Anonymous arguments

An anonymous argument is just an expression followed by a semicolon.

AnonymousArgument: Expression ";"

The type of the argument is the type of the expression.

{
    Head { title="Hello"; };
    Body {
        Div { "Hello " name "!" };
    };
}

6.6.10. Specified arguments

A specified argument is a value specification statement or lazy specification statement, as defined in §5.2.3 Specification statements, where the value reference or callable reference is treated as the name of a parameter of the invoked function or class instead of using the usual rules for resolving unqualified names.

SpecifiedArgument: Specification
  • If a specified argument is a value specification statement, its type is the type of the specified expression.

  • If a specified argument is a lazy specification statement with no parameter lists, its type is the type of the specified expression.

  • Otherwise, if it is a lazy specification statement with a parameter list, its type is the callable type formed from the type of the expression, interpreted as a function return type, and the types of its parameter lists, according to §4.7.1 Callable type of a function.

Note: there is an ambiguity here between assignment expressions and specified arguments. This ambiguity is resolved in favor of interpreting the argument as a specified argument. Therefore an anonymous argument in a named argument list may not be an assignment expression.

{ 
    product = getProduct(id); 
    quantity = 1; 
}
{ 
    by(Value x, Value y) => x<=>y;
}

6.6.11. Inline declaration arguments

An inline declaration argument defines a getter, function, or anonymous class, and assigns it to a parameter.

ValueArgument | FunctionArgument | ObjectArgument

An inline getter argument is a streamlined getter declaration, as defined in §4.8.2 Getters. The type of the argument is the declared or inferred type of the getter.

ValueArgument: ValueHeader (Block | (Specifier | LazySpecifier) ";")

An inline function argument is a streamlined function declaration, as defined in §4.7 Functions. The type of the argument is the callable type of the function, as defined by §4.7.1 Callable type of a function.

FunctionArgument: FunctionHeader (Block | LazySpecifier ";")

An inline anonymous class argument is a streamlined anonymous class declaration, as defined in §4.5.7 Anonymous classes. The type of the argument is the anonymous class type.

ObjectArgument: ObjectHeader ClassBody

A named argument may not have type parameters or annotations.

{
    description = "Total";
    value amount { 
        variable Float total = 0.0;
        for (Item item in items) {
            sum += item.amount;
        }
        return total;
    }
}
{ 
    label = "Say Hello"; 
    void onClick() { 
        say("Hello!"); 
    } 
}
{ 
    function by(Value x, Value y) => x<=>y;
}
{
    object iterator 
            satisfies Iterator<Order> {
        variable value done = false;
        shared actual Order|Finished next() {
            if (done) {
                return finished;
            }
            else {
                done=true;
                return order; 
            }
        }
    }   
}

6.6.12. Iterable and tuple enumeration

An enumeration expression is an abbreviation for tuple and iterable object instantiation. Iterable enumerations are delimited using braces. Tuple enumerations are delimited by brackets.

Iterable: "{" ArgumentList "}"
Tuple: "[" ArgumentList "]"

The type of an iterable enumeration expression is:

  • Empty if there are no argument expressions, or
  • Iterable<U,Nothing> where U, the argument expression list is an invariant suptype of U[].

The type of a tuple enumeration expression is the type of the list of arguments it contains.

{String+} = { "hello", "world" };
[] none = [];
[Float,Float] xy = [x, y];
[Float,Float, String*] xy = [x, y, *labels];

Every argument expression must have a type, even if the enumeration expression occurs in a dynamic block.

6.6.13. Dynamic enumerations

A dynamic enumeration expression creates a new object with no class by enumerating its members, allowing interoperation with dynamically typed native code.

DynamicValue: "value" NamedArguments

A dynamic enumeration expression has no type.

Any argument names may be specified in the named argument list.

A dynamic enumeration expression must occur inside a dynamic block.

The semantics of this construct are platform-dependent and beyond the scope of this specification.

6.7. Conditional expressions and anonymous class expressions

A conditional expression resembles a control structure but is part of the expression syntax, and evaluates to a value.

An inline class is an anonymous class defined within an expression.

6.7.1. Inline conditional expressions

We plan to support inline if/then/else conditional expressions, for example:

Integer port = if (exists setting = process.propertyValue("port")) 
                        then parseInteger(setting) else 8080;

Note that this is more powerful than the then and else operators because it allows all kinds of conditions, not only boolean conditions.

Should we also support:

  • inline switch/case/else conditional expressions, or even

  • inline try/catch exceptional conditions?

For example:

Float evaluated => switch (expr)
        case (is Literal) expr.integer
        case (is Plus) expr.left.evaluated + expr.right.evaluated
        case (is Times) expr.left.evaluated * expr.right.evaluated;

6.7.2. Let expressions

Should we support let expressions, possibly reusing the keyword given?

given (dist = sqrt(x^2+y^2)) [x/dist,y/dist]

6.7.3. Inline anonymous class expressions

Should we support inline object declarations, for example:

iterator => object satisfies Iterable<Nothing> { next() => finished; }

Or, alternatively, a kind of named argument ""instantiation" syntax for interfaces and abstract classes:

iterator => Iterable { next() => finished; }

The first option is more flexible, but also more verbose. The second is streamlined for the common case and might even be able to do type argument inference as shown here.

If we go with the first option, should we support inline class declarations? This would be like an inline object and an anonymous function rolled into one. We could even support class arguments in named argument lists.

6.8. Operators

Operators are syntactic shorthand for more complex expressions involving invocation, evaluation, or instantiation. There is no support for user-defined operator overloading:

  • new operator symbols may not be defined outside of the operators specified below, and

  • the definition of the operators specified below may not be changed or overloaded.

However, many of the operators below are defined in terms of default or formal methods or attributes. So, within well-defined limits a concrete implementation may customize the behavior of an operator. This approach is called operator polymorphism.

Some examples:

Float z = x * y + 1.0;
even = n % 2 == 0;
++count;
Integer j = i++;
if ( x > 100 || x < 0 ) { ... }
User user = users[userId] else guest;
List<Item> firstPage = results[0..20];
for (n in 0:length) { ... }
if (char in 'A'..'Z') { ... }
String[] names = people*.name;
this.total += item.price * item.quantity;
Float vol = length^3;
Vector scaled = scale ** vector;
map.define(person.name->person);
if (!document.internal || user is Employee) { ... }

6.8.1. Operator precedence

There are 18 distinct operator precedence levels, but these levels are arranged into layers in order to make them easier to predict.

  • Operators in layer 1 produce, transform, and combine values.

  • Operators in layer 2 compare or predicate values, producing a Boolean result.

  • Operators in layer 3 are logical operators that operate upon Boolean arguments to produce a Boolean value.

  • Operators in layer 4 perform assignment and conditional evaluation.

Within each layer, postfix operators have a higher precedence than prefix operators, and prefix operators have a higher precedence than binary operators.

There is a single exception to this principal: the binary exponentiation operator ^ has a higher precedence than the prefix operators + and -. The reason for this is that the following expressions should be equivalent:

-x^2       //means -(x^2)
0 - x^2    //means 0 - (x^2)

This table defines the relative precedence of the various operators, from highest to lowest, along with associativity rules:

Table 6.1. 

OperationsOperatorsTypeAssociativity
Layer 1
Member invocation and selection, index, span, postfix increment, postfix decrement:., *., ?., (), {}, [], [:], [..], [...], ++, --Binary / ternary / N-ary / unary postfixLeft
Prefix increment, prefix decrement:++, --Unary prefixRight
Exponentiation:^BinaryRight
Negation:+, -Unary prefixRight
Set intersection:&BinaryLeft
Set union and complement:|, ~BinaryLeft
Multiplication, division, remainder:*, /, %BinaryLeft
Scale:**BinaryRight
Addition, subtraction:+, -BinaryLeft
Range and entry construction:.., :, ->BinaryNone
Layer 2
Existence, emptiness:exists, nonemptyUnary postfixNone
Comparison, containment, assignability, inheritance:<=>, <, >, <=, >=, in, is, of, satisfiesBinary (and ternary)None
Equality, identity:==, !=, ===BinaryNone
Layer 3
Logical not:!Unary prefixRight
Logical and:&&BinaryLeft
Logical or:||BinaryLeft
Layer 4
Conditionals:then, elseBinaryLeft
Assignment:=, +=, -=, *=, /=, %=, &=, |=, ^=, ~=, &&=, ||=BinaryRight

It's important to be aware that in Ceylon, compared to other C-like languages, the logical not operator ! has a very low precedence. The following expressions are equivalent:

!x.y == 0.0  //means !(x.y == 0.0)
x.y != 0.0

6.8.2. Operator definition

The following tables define the semantics of the Ceylon operators. There are six basic operators which do not have a definition in terms of other operators or invocations:

  • the member selection operator . separates the receiver expression and member name in a member expression, as defined above in §6.5.2 Member expressions,

  • the argument specification operators () and {} specify the argument list of an invocation, as defined in §6.6 Invocation expressions and §8.4.4 Invocation,

  • the assignment operator = assigns a new value to a variable and returns the new value after assignment, as defined in §8.4.3 Assignment,

  • the identity operator === evaluates to true if its argument expressions evaluate to references to the same object, as defined in §8.1 Object instances, identity, and reference passing, or to false otherwise,

  • the assignability operator is evaluates to true if its argument expression evaluates to an instance of a class, as defined in §8.1 Object instances, identity, and reference passing, that is a subtype of the specified type, or to false otherwise, and

  • the coverage operator of narrows or widens the type of an expression to any specified type that covers the expression type, as defined by §3.4.1 Coverage, without affecting the value of the expression.

All other operators are defined below in terms of other operators and/or invocations.

In the tables, the following pseudo-code is used, which is not legal Ceylon syntax:

First,

if (b) then x else y   //pseudocode

means the value of result after execution of the following:

X result; if (b) { result=x; } else { result=y; }

Second,

let t=x in y   //pseudocode

means the value of result after execution of the following:

X t = x; Y result=y;

6.8.3. Basic invocation and assignment operators

These operators support method invocation and attribute evaluation and assignment.

Table 6.2. 

ExampleNameDefinitionLHS typeRHS typeReturn type
Invocation
lhs.membermember Xa member of X, of type TT
lhs(x,y,z) or lhs{a=x;b=y;}invoke Callable <T,P>argument list of type PT
Assignment
lhs = rhsassignvariable of type XXX
Coverage
lhs of TypeofXa literal type T that covers XT

6.8.4. Equality and comparison operators

These operators compare values for equality, order, magnitude, or membership, producing boolean values.

Table 6.3. 

ExampleNameDefinitionLHS typeRHS typeReturn type
Equality and identity
lhs === rhsidenticalidentical(lhs,rhs)X given X satisfies IdentifiableY given Y satisfies Identifiable where X&Y is not NothingBoolean
lhs == rhsequallhs.equals(rhs)ObjectObjectBoolean
lhs != rhsnot equal!lhs.equals(rhs)ObjectObjectBoolean
Comparison
lhs <=> rhscomparelhs.compare(rhs)Comparable <T>TComparison
lhs < rhssmallerlhs.compare(rhs)==smallerComparable <T>TBoolean
lhs > rhslargerlhs.compare(rhs)==largerComparable <T>TBoolean
lhs <= rhssmall aslhs.compare(rhs)!=largerComparable <T>TBoolean
lhs >= rhslarge aslhs.compare(rhs)!=smallerComparable <T>TBoolean
Containment
lhs in rhsinlet x=lhs in rhs.contains(x)ObjectCategoryBoolean
Assignability
rhs is Typeis any type which is not a subtype of T, whose intersection with T is not Nothingany literal type TBoolean

TODO: Should we have allow the operators <= and >= to handle partial orders? A particular usecase is Set comparison.

A bounded comparison is an abbreviation for two binary comparisons:

  • l<x<u means x>l && x<u,

  • l<=x<u means x>=l && x<u,

  • l<x<=u means x>l && x<=u, and

  • l<=x<=u means x>=l && x<=u

for expressions l, u, and x.

These abbreviations have the same precedence as the binary < and <= operators, and, like the binary forms, are not associative.

6.8.5. Logical operators

These are the usual logical operations for boolean values.

Table 6.4. 

ExampleNameDefinitionLHS typeRHS typeReturn type
Logical operators
!rhsnotif (rhs) false else true BooleanBoolean
lhs || rhsconditional orif (lhs) true else rhsBooleanBooleanBoolean
lhs && rhsconditional andif (lhs) rhs else falseBooleanBooleanBoolean
Logical assignment
lhs ||= rhsconditional orif (lhs) true else lhs=rhsvariable of type BooleanBooleanBoolean
lhs &&= rhsconditional andif (lhs) lhs=rhs else falsevariable of type BooleanBooleanBoolean

6.8.6. Operators for handling null values

These operators make it easy to work with optional expressions.

Table 6.5. 

ExampleNameDefinitionLHS typeRHS typeReturn type
Existence
lhs existsexistsif (exists lhs) true else falseany type whose intersections with Object and Null are not Nothing Boolean
lhs nonemptynonemptyif (nonempty lhs) true else falseany subtype of Anything[]? whose intersections with [] and [Nothing+] are not Nothing Boolean
Nullsafe invocation
lhs?.membernullsafe attributeif (exists lhs) lhs.member else nullX?an attribute of type T of XT?
lhs?.membernullsafe method X?a method of callable type Callable <T,P> of XCallable <T?,P>

6.8.7. Correspondence and sequence operators

These operators provide a simplified syntax for accessing values of a Correspondence, and for joining and obtaining subranges of Sequences.

Table 6.6. 

ExampleNameDefinitionLHS typeRHS typeReturn type
Keyed item access
lhs[index]lookuplhs.item(index)Correspondence<X,Y>XY?
Spans and segments
lhs[from:length]segmentlhs.segment(from,length)Ranged<X,Y>X, IntegerY
lhs[from..to]spanlhs.span(from,to)Ranged<X,Y>X, XY
lhs[from...]upper spanlhs.spanFrom(from)Ranged<X,Y>XY
lhs[...to]lower spanlhs.spanTo(to)Ranged<X,Y>XY
Spread invocation
lhs*.memberspread attribute[ for (X x in lhs) x.member ]X[]attribite of X of type TT[]
lhs*.memberspread method X[]method of X of callable type Callable <T,P>Callable <T[],P>
Spread multiplication
lhs ** rhsscalerhs.scale(lhs)YScalable<X,Y>X

There are two special cases related to sequences. A type X is a sequence type if X is a subtype of Sequential<Anything>.

For any sequence type X and integer n, we can form the nth tail type, Xn, of X as follows:

  • for every i<=0, Xi is X, and

  • for every i>0, if Xi has the principal instantiation Tuple<Ui,Fi,Yi> then X(i+1) is Yi, or, otherwise, X(i+1) is Xi.

For any sequence type X and integer n, we can form the nth element type, En, of X as follows:

  • if Xn has the principal instantiation Tuple<Un,Fn,Yn> then En is Fn, or, otherwise, Xn has the principal instantiation Sequential<Fn> and En is Fn?.

Then the two special cases are:

  • The type of an expression of form x[i...] where x is of tuple type X and n is an integer literal is Xn.

  • The type of an expression of form x[i] where x is of tuple type X and n is an integer literal is En.

6.8.8. Operators for creating objects

These operators simplify the syntax for instantiating certain commonly used built-in types.

Table 6.7. 

ExampleNameDefinitionLHS typeRHS typeReturn type
Range and entry constructors
lhs..rhsspanned rangeRange(lhs, rhs)T given T satisfies Ordinal & Comparable<T>TRange<T>
lhs:rhssegmented rangeif (lhs<=0) [] else TODOT given T satisfies Ordinal & Comparable<T>IntegerT[]
lhs->rhsentryEntry(lhs, rhs)U given U satisfies ObjectV given V satisfies ObjectEntry<U,V>

6.8.9. Conditional operators

Two special operators allow emulation of the famous ternary operator of C-like languages.

Table 6.8. 

ExampleNameDefinitionLHS typeRHS typeReturn type
Conditionals
lhs then rhsthenif (lhs) then rhs else nullBooleanT given T satisfies ObjectT?
lhs else rhselseif (exists lhs) then lhs else rhsU such that null is UVU&Object|V

6.8.10. Arithmetic operators

These are the usual mathematical operations for all kinds of numeric values.

Table 6.9. 

ExampleNameDefinitionLHS typeRHS typeReturn type
Increment, decrement
++rhssuccessorrhs=rhs.successor variable of type Ordinal<T>T
--rhspredecessorrhs=rhs.predecessor variable of type Ordinal<T>T
lhs++increment(++lhs).predecessorvariable of type Ordinal<T> T
lhs--decrement(--lhs).successorvariable of type Ordinal<T> T
Numeric operators
+rhs rhs.positiveValue Invertable <I>I
-rhsnegationrhs.negativeValue Invertable <I>I
lhs + rhssumlhs.plus(rhs)Summable<X>XX
lhs - rhsdifferencelhs.minus(rhs)Subtractable <X>XX
lhs * rhsproductlhs.times(rhs)Numeric<X>XX
lhs / rhsquotientlhs.divided(rhs)Numeric<X>XX
lhs % rhsremainderlhs.remainder(rhs)Integral<X>XX
lhs ^ rhspowerlhs.power(rhs)Exponentiable <X,Y>YX
Numeric assignment
lhs += rhsaddlhs=lhs.plus(rhs)variable of type Summable<N>NN
lhs -= rhssubtractlhs=lhs.minus(rhs)variable of type Subtractable <N>NN
lhs *= rhsmultiplylhs=lhs.times(rhs)variable of type Numeric<N>NN
lhs /= rhsdividelhs=lhs.divided(rhs)variable of type Numeric<N>NN
lhs %= rhsremainderlhs=lhs.remainder(rhs)variable of type Integral<N>NN

Arithmetic operators automatically widen from Integer to Float when necessary. If one operand expression is of static type Integer, and the other is of type Float, the operand of type Integer is widened to a Float in order to make the operator expression well-typed. Widening is performed by evaluating the attribute float defined by Integer.

Note: this is the only circumstance in the language where implicit type conversion occurs. In fact, it is more correct to view this behavior as an instance of operator overloading than as an implicit type conversion. Implicit widening does not occur when an expression of type Integer is merely assigned to the type Float, since such behavior would result in ambiguities when generics come into play.

6.8.11. Set operators

These operators provide traditional mathematical operations for sets.

Table 6.10. 

ExampleNameDefinitionLHS typeRHS typeReturn type
Set operators
lhs | rhsunionlhs.union(rhs)Set<X>Set<Y>Set<X|Y>
lhs & rhsintersectionlhs.intersection(rhs)Set<X>Set<Y>Set<X&Y>
lhs ~ rhscomplementlhs.complement(rhs)Set<X>Set<Object>Set<X>
Set assignment
lhs |= rhsunionlhs=lhs|rhsvariable of type Set<X>Set<X>Set<X>
lhs &= rhsintersectionlhs=lhs&rhsvariable of type Set<X>Set<Object>Set<X>
lhs ~= rhscomplementlhs=lhs~rhsvariable of type Set<X>Set<Object>Set<X>

6.9. Operator-style member and invocation expressions

An member expression or a method invocation with a single positional argument may be written using an operator-style syntax. This syntax has an extremely low precedence, just above the precedence of the assignment operator, is not associative, and does not form a legal expression statement.

0..max by step

In an operator-style invocation expression, the invoked method name and optional type arguments occur in an infix location between two expressions. The first expression is interpreted as the receiver expression, and the second expression is interpreted as a positional argument to the first parameter of the method.

OperatorInvocation: ValueExpression MemberName TypeArguments? Expression

The semantics of this syntax are identical to ordinary invocation expressions, as defined in §6.6 Invocation expressions.

In an operator-style member expression, the member name and optional type arguments occur in a postfix location after an expression. The expression is interpreted as the receiver expression.

OperatorMemberExpression: ValueExpression MemberName TypeArguments?

The semantics of this syntax are identical to ordinary member expression, as defined in §6.5.2 Member expressions.

TODO: Should we rather use this syntax as a sugar for invocation of a toplevel function?

6.10. Metamodel expressions

A metamodel expression is a reference to a type, a class, a function, or a value. It evaluates to a metamodel object whose static type captures the type itself, the callable type of the class, the callable type of the function, or the type of the value, respectively.

Meta: TypeMeta | BaseMeta | MemberMeta

A type metamodel expression is a type, as defined by §3.2 Types, surrounded by backticks.

TypeMeta: "`" Type "`"

The type may or may not be a reference to a class or interface.

Class<Person,[Name]> personClass = `Person`;
Interface<List<String>> stringListInterface = `List<String>`;
UnionType<Integer|Float> numberType = `Number`;
Type<Element> elementType = `Element`;

A base metamodel expression is a member name, with an optional list of type arguments, surrounded by backticks.

BaseMeta: "`" MemberName TypeArguments? "`"

A base metamodel expression is a reference to a value or function. The referenced declaration is determined according to §5.1.7 Unqualified reference resolution.

A member metamodel expression is a type, as defined by §3.2 Types, followed by a member name, with an optional list of type arguments, surrounded by backticks.

MemberMeta: "`" (QualifiedType|GroupedType) "." MemberName TypeArguments? "`"

A member metamodel expression is a reference to an attribute or method of the type. The member is resolved as a member of the type according to §5.1.8 Qualified reference resolution.

Function<Float,[{Float+}]> sumFunction = `sum<Float>`;
Attribute<Person,String> personNameAttribute = `Person.name`;
Method<Person,Anything,[String]> personSayMethod = `Person.say`;

Type argument inference is impossible in a metamodel expression, so type arguments must be explicitly provided for every generic declaration.

6.10.1. Type of a metamodel expression

The type of a metamodel expression depends upon the kind of declaration referenced:

  • for a toplevel value of type R, the type is Value<R>,

  • for a toplevel function of callable type Callable<R,P>, the type is Function<R,P>,

  • for a toplevel class of callable type Callable<R,P>, the type is Class<R,P>,

  • for a class nested in a block of callable type Callable<R,P>, the type is Class<R,Nothing>, and

  • for a toplevel interface or interface nested in a block of type R, the type is Interface<R>.

Furthermore, given a member of a type T:

  • for an attribute of type R, the type is Attribute<T,R>,

  • for a method of callable type Callable<R,P>, the type is Method<T,R,P>,

  • for a member class of callable type Callable<R,P>, the type is MemberClass<T,R,P>, and

  • for a nested interface of type R, the type is MemberInterface<T,R>.

Finally:

  • for a union type T, the type is UnionType<T>,

  • for an intersection type T, the type is IntersectionType<T>,

  • for the type Nothing, the type is Type<Nothing>, and

  • for a type parameter T, the type is Type<T>.

If a type alias occurs inside a typed metamodel expression, it is replaced by its definition, after substituting type arguments, before determining the type of the metamodel expression.

6.11. Reference expressions

A reference expression is a reference to a program element and evaluates to a detyped metamodel of the program element. Reference expressions are used primarily in annotations, especially the documentation annotations listed in §7.4.2 Documentation. A reference expression may refer to:

  • a class, interface, type alias, or type parameter,

  • a function or value, or

  • a package or module.

Dec: TypeDec | MemberDec | PackageDec | ModuleDec

6.11.1. Declaration references

A class reference expression, interface reference expression, alias reference expression, or type parameter reference expression is a series of initial uppercase identifiers, with the keyword class, interface, alias, or given, respectively, surrounded by backticks.

TypeDec: "`" ("class" | "interface" | "alias" | "given") (TypeName ".")* TypeName "`"

A value reference expression or function reference expression is an initial lowercase identifier, qualified by a list of initial uppercase identifiers, with the keyword value or function, surrounded by backticks.

MemberDec: "`" ("value" | "function") (TypeName ".")* MemberName "`"

A reference expression is a reference to a declaration. The referenced declaration is determined according to §5.1.7 Unqualified reference resolution and §5.1.8 Qualified reference resolution. The kind of the referenced declaration must match the kind of reference indicated by the keyword.

ClassDeclaration personClass = `class Person`;
InterfaceDeclaration stringListInterface = `interface List`;
AliasDeclaration numberAlias = `alias Number`;
TypeParameter elementTypeParameter = `given Element`;
ValueDeclaration personNameAttribute = `value Person.name`;
FunctionDeclaration personSayMethod = `function Person.say`;

6.11.2. Package and module references

A package reference expression is a package name, as defined by §4.1.2 Packages, with the keyword package, surrounded by backticks.

PackageDec: "`" "package" FullPackageName "`"

The package name must refer to a package from which an import statement in the same compilation unit may import declarations, as defined by §4.2 Imports.

Package modelPackage = `package ceylon.language.meta.model`;

A module reference expression is a module name, as defined by §9.3.1 Module names and version identifiers, with the keyword module, surrounded by backticks.

ModuleDec: "`" "module" FullPackageName "`"

The module name must refer to the module to which the compilation unit belongs, as specified by §9.2 Source layout, or to a module imported by the module to which the compilation unit belongs, as defined by §9.3.12 Module descriptors.

Module languageModule = `module ceylon.language`;

6.11.3. Type of a reference expression

The type of a reference expression depends upon the kind of program element referenced:

  • for a module, the type is Module,

  • for a package, the type is Package,

  • for a value, the type is ValueDeclaration,

  • for a function, the type is FunctionDeclaration,

  • for a type parameter, the type is TypeParameter,

  • for a type alias declared using the keyword alias, the type is AliasDeclaration,

  • for a class or class alias, the type is ClassDeclaration, and

  • for an interface or interface alias, the type is InterfaceDeclaration.