Chapter 8. Execution

A Ceylon program executes in a virtual machine environment, either:

In future, other virtual machine architectures may be supported.

Despite the obvious differences between the respective languages that these virtual machines were designed for, they share very much in common in terms of runtime semantics, including common notions such as object identity, primitive value types, exceptions, garbage collection, dynamic dispatch, and pass by reference.

Ceylon abstracts away many of the differences between these platforms, and reuses what is common between them. Inevitably there are some differences that can't reasonably be hidden from the Ceylon program, and the programmer must take these differences into consideration.

In Ceylon, every value is a reference to an instance of a class, except within a dynamic block, where a value with no type may be a reference to an object which is not an instance of a class.

Note: the semantics of objects without classes is platform-dependent and outside the scope of this specification.

8.1. Object instances, identity, and reference passing

An object is a unique identifier, together with a reference to a class, its type arguments, and a persistent value for each reference declared by the class (including inherited references). The object is said to be an instance of the class.

A value is a reference to an object (a copy of its unique identifier). At a particular point in the execution of the program, every reference of every object that exists, and every initialized reference of every function, getter, setter, or initializer that is currently executing has a value. Furthermore, every time an expression is executed, it produces a value.

Two values are said to be identical if they are references to the same object—if they hold the same unique identifier. The program may determine if two values of type Identifiable are identical using the === operator defined in §6.8.2 Operator definition. It may not directly obtain the unique identifier (which is a purely abstract construct). The program has no way of determining the identity of a value which is not of type Identifiable.

Given a value, the program may determine if the referenced object is assignable to a certain type using the is operator. The object is assignable to the given type if the applied type formed by its class and type arguments is a subtype of the given type according to the type system defined in Chapter 3, Type system. (Therefore, the Ceylon runtime must be capable of reasoning about subtyping.)

Invocation of a function or instantiation of a class results in execution of the function body or class initializer with parameter values that are copies of the value produced by executing the argument expressions of the invocation, and a reference to the receiving instance that is a copy of the value produced by executing the receiver expression. The value produced by the invocation expression is a copy of the value produced by execution of the return directive expression.

Person myself(Person me) { return me; }
Person p = ...;
assert (myself(p)===p); //assertion never fails
Semaphore s = Semaphore();
this.semaphore = s;
assert (semaphore===s); //assertion never fails

A new object is produced by execution of a class instantiation expression. The Ceylon compiler guarantees that if execution of a class initializer terminates with no uncaught exception, then every reference of the object has been initialized with a well-defined persistent value. The value of a reference is initialized for the first time by execution of a specifier or assignment expression. Every class instantiation expression results in an object with a new unique identifier shared by no other existing object. The object exists from the point at which execution of its initializer terminates. Conceptually, the object exists until execution of the program terminates.

In practice, the object exists at least until the point at which it is not reachable by recursively following references from any function, getter, setter, or initializer currently being executed, or from an expression in a statement currently being executed. At this point, its persistent values are no longer accessible to expressions which subsequently execute and the object may be destroyed by the virtual machine. There is no way for the program to determine that an object has been destroyed by the virtual machine (Ceylon does not support finalizers).

8.1.1. Value type optimizations

As a special exception to the rules defined above, the compiler is permitted to emit bytecode or compiled JavaScript that produces a new instance of certain types in the module ceylon.language without execution of the initializer of the class, whenever any expression is evaluated. These types are: Integer, Float, Character, Range, Entry, String, Array, and Tuple. Furthermore, it is permitted to use such a newly-produced instance as the value of the expression, as long as the newly-produced instance is equal to the value expected according to the rules above, as determined using the == operator.

Therefore, the types listed above directly extend Object instead of Basic, and are not Identifiable.

Note: this does no justice at all to our compiler. Actually the compiler infrastructure already supports value type optimization for user-defined types, though we have not yet exposed this functionality as part of the language.

8.1.2. Type argument reification

Type arguments, as defined in §3.6 Generic type arguments, are reified in Ceylon. An instance of a generic type holds a reference to each of its type arguments. Therefore, the following are possible in Ceylon:

  • testing the runtime value of a type argument of an instance, for example, objectList is List<Person> or case (is List<Person>),

  • filtering exceptions based on type arguments, for example, catch (NotFoundException<Person> pnfe), and

  • testing the runtime value of an instance against a type parameter, for example x is Key, or against a type with a type parameter as an argument, for example, objectList is List<Element>.

  • obtaining a Type object representing a type with type arguments, for example, `List<Person>`,

  • obtaining a Type object representing the runtime value of a type parameter, for example, `Element`, or of a type with a type parameter as an argument, for example, `List<Element>`, and

  • obtaining a Type object representing the runtime value of a type argument of an instance using reflection, for example, type(objectList).typeArguments.first.

At runtime, all types are concrete types formed by:

  • recursively replacing all type aliases, class aliases, and interface aliases with their definitions, which is always possible according to §3.2.10 Type alias elimination, and

  • recursively replacing all type parameters with their type arguments

in any type that appears in an expression or condition.

Therefore, every type parameter refers, at runtime, to a concrete type that involves no type aliases or type parameters. In particular, the type arguments held by an instance of a generic class are concrete types.

This program prints String[].

class Generic<out T>(T t) { string=>`T`.string; }
Generic<{S*}> gen<S>(S* ss) => Generic(ss);
void run() {
    print(gen("hello", "world"));
}

The runtime is generally permitted, as an optimization, to return a more precise type in place of a less precise type when a type parameter is evaluated. This program may print String instead of Object, even though Object is the type argument inferred at compile time.

class Generic<out T>(T t) { string=>`T`.string; }
Generic<Object> gen(Object o) => Generic(o);
void run() {
    print(gen("hello"));
}

8.2. Sequential execution and closure

Ceylon programs are organized into bodies, as defined in §5.1 Block structure and references, containing statements which are executed sequentially and have access to declarations which occur in the surrounding lexical context and to persistent values held by references, as defined in §4.8.1 References, declared in the surrounding lexical context.

Note: for the purposes of this section, an interface body is, strictly speaking, a trivial case of a body which contains no statements or persistent values, but we're primarily concerned with blocks and class bodies.

The statements and non-lazy specifiers that directly occur in a body are executed sequentially in the lexical order in which they occcur. Execution of a body begins at the first statement or non-lazy specifier. Execution of a block terminates when the last statement or non-lazy specifier of the body finishes executing, or when a control directive that terminates the block is executed, or when an exception is thrown by an evaluation, assignment, invocation, or instantiation.

8.2.1. Frames

When execution of a body begins, a frame is created. For each reference whose declaration directly occurs in the body, the frame has a value, which may or may not be initialized. The value may be initialized or assigned during execution of the body.

We can visualize a frame as a list of reference declarations with optional values. For example, a frame with an initialized reference named language and an uninitialized reference named count would be written like this:

{ String language = "ceylon"; Integer count; }

While a body is executing, all values held in the frame are considered accessible. An evaluation, assignment, invocation, or instantiation may result in a pause in execution of the body while the called getter, setter, function, or class is executed or instantiated. However, the frame associated with the calling body is retained and values held in the frame are still considered accessible. When execution of the body resumes, the frame is restored.

When execution of a body terminates, the frame may or may not become inaccessible. In the case of a class body, if the initializer terminates with no thrown exception, the frame and its values become a new instance of the class, are associated with the newly created unique identifier, and remain accessible while this object is itself accessible. In the case of any other kind of body, or in the case that an initializer throws an exception, the frame and its values may remain accessible if:

  • a reference to a function or class declared within the body is accessible,

  • an instance of a class declared within the body is accessible, or

  • an instance of a comprehension declared within the body is accessible.

Otherwise, the frame becomes inaccessible and may be destroyed.

The principle of closure states that a nested body always has access to a frame for every containing body. The set of current instances of containing classes and current frames of containing blocks forms the closure of a nested declaration.

8.2.2. Current instances and current frames

A frame may be the current frame for a body. When the body is executing, the created frame is the current frame. When execution of the body terminates, the created frame is no longer the current frame. Invocation or evaluation of a member of a class or interface, invocation of a callable reference or anonymous function, or evaluation of the values produced by a comprehension may result in the frame being restored as the current frame.

A class instance, callable reference, anonymous function reference, or comprehension instance packages a reference to a frame for each body containing the program element, as specified below. When a member of the class instance is invoked or evaluated, when the callable reference or anonymous function is invoked, or when the comprehension instance produces a value, these frames are restored as the current frames of the associated bodies. When the invocation or evaluation terminates, or when the comprehension value has been produced, these frames are no longer current frames.

The value associated with a value reference in the current frame of the body to which the value reference belongs is called the current value of the value reference.

If a frame is the current frame for a class or interface body, we call it the current instance of the class or interface.

TODO: in the following two sections, account for callable references, anonymous function references, and comprehension instances.

8.2.3. Current instance of a class or interface

If a statement is occurs directly or indirectly inside a class or interface body, then there is always a current instance of the class or interface when the statement is executed. The current instance is determined as follows:

  • For a statement that occurs sequentially, as defined by §5.1 Block structure and references, in the body of the class or of a constructor of the class, the current instance is the new instance being initialized.

  • For a statement that occurs sequentially in the body of a member of the class or interface, the current instance is the receiving instance of the base or member expression that resulted in a reference to the member.

  • For a statement that occurs sequentially in the body of a nested class or interface that occurs in the body of the class or interface, the current instance is the same object that was the current instance when the initializer of the current instance of the nested class or interface was executed.

  • Otherwise, for any other statement that occurs sequentially in the body of a declaration that occurs in the body of the class or interface, the current instance is the same object that was the current instance when the base member expression that resulted in a reference to the declaration was executed.

Here, innerObject is the current instance of Inner when member() is executed, and outerObject is the current instance of Outer:

Outer outerObject = Outer();
Inner innerObject = outerObject.Inner();
innerObject.member();

8.2.4. Current frame of a block

If a statement occurs directly or indirectly inside a block, then there is always a current frame of the block when the statement is executed. The current frame is determined as follows:

  • If the statement occurs sequentially, as defined by §5.1 Block structure and references, in the block, the current frame is the frame associated with the current execution of the block.

  • For a statement that occurs sequentially in the body of a nested class or interface that occurs in the block, the current frame is the same frame that was the current frame when the initializer of the current instance of the nested class or interface was executed.

  • Otherwise, for any other statement that occurs sequentially inside the body of a declaration that occurs in the block, and the current frame is the frame that was the current frame when the base member expression that resulted in a reference to the declaration was executed.

In each of the following code fragments, result refers to the value "hello":

String()() outerMethod(String s) {
    String() middleMethod() {
        String innerMethod() => s;
        return innerMethod;
    }
    return middleMethod;
}

String middleMethod()() => outerMethod("hello");
String innerMethod() => middleMethod();
String result = innerMethod();
Object outerMethod(String s) {
    object middleObject {
        shared actual String string => s;
    }
    return middleObject;
}

Object middleObject = outerMethod("hello");
String result = middleObject.string;

8.2.5. Initialization

When an instance is instantiated, its initializer is executed, and the initializer for every class it inherits is executed. If a class has constructors, one of its constructors is also executed. For a class C:

  • First, the initializer of Object defined in ceylon.language is executed. (This initializer is empty and does no work.)

  • For each superclass X of C, there is exactly one other superclass Y of C that directly extends X. When execution of the initializer of X terminates without a thrown exception, execution of the initializer of Y begins. When sequential execution of the initializer reaches the declaration of the invoked or delegated constructor of Y, if any, the constructor itself is executed. When execution of the constructor terminates without a thrown exception, execution of the initializer resumes at the next statement after the constructor declaration.

  • Finally, when execution of the initializer of C, terminates without a thrown exception, the new instance of C is fully-initialized and made accessible to the calling code.

If any initializer or constructor in the class hierarchy terminates with a thrown exception, initialization terminates and the incompletely-initialized instance never becomes accessible.

Each initializer produces a frame containing initialized values for each reference declared by the corresponding class. These frames are aggregated together to form the new instance of the class C.

Note: since interfaces don't have initializers, the issue of "linearization" of supertypes simply never arises in Ceylon. There is a natural, well-defined initialization ordering.

8.2.6. Class instance optimization

As an exception to the above, the compiler is permitted to destroy a persistent value associated with a class instance when the class initializer terminates, potentially rendering inaccessible the instance identified by the value, if it can determine that the persistent value will never be subsequently accessed by the program.

This optimization is the only source of a distinction between a "field" of a class and a "local variable" of its initializer. There is no way for a program to observe this distinction.

8.2.7. Execution of expression and specification statements

When an expression statement is executed, the expression is evaluated.

When a non-lazy specification statement is executed, the specified expression is evaluated, and the resulting value assigned to the specified reference within the current frame or current instance associated with the body to which the specified reference belongs.

When a lazy specification statement is executed, the specified expression is associated with the specified reference within the current frame or current instance associated with the body to which the specified reference belongs. Subsequent evaluation or invocation of the reference for this current frame or current instance may result in evaluation of the specified expression, in which case the expression is evaluated within this current frame or current instance.

8.2.8. Execution of control directives

Execution of a control directive, as specified in §5.3.2 Control directives, terminates execution of the body in which it occurs, and possibly of other containing bodies.

  • A return directive that occurs sequentially in the body of a function, getter, setter, or class initializer terminates execution of the body of the function, getter, setter, or class initializer and of all intervening bodies. Optionally, it determines the return value of the function or getter.

  • A break directive terminates execution of the body of the most nested containing loop in which it occurs sequentially, and of all intervening bodies. Additionally, it terminates execution of the loop.

  • A continue directive terminates execution of the body of the most nested containing loop in which it occurs sequentially, and of all intervening bodies. It does not terminate execution of the loop.

  • A throw directive that occurs sequentially in the body of a function, getter, setter, or class initializer terminates execution of the body of the function, getter, setter, or class initializer and of all intervening bodies, and, furthermore, the exception propagates to the caller, as defined below, unless there is an intervening try with a catch clause matching the thrown exception, in which case it terminates execution of the body of the try statement and all intervening bodies, and execution continues from the body of the catch clause.

8.2.9. Exception propagation

If execution of an evaluation, assignment, invocation, or instantiation terminates with an exception thrown, the exception propagates to the calling code, and terminates execution of the body of the function, getter, setter, or class initializer in which the expression involving the evaluation, assignment, invocation, or instantiation sequentially occurs, and of all intervening bodies, and, furthermore, the exception propagates to the caller unless there is an intervening try with a catch clause matching the thrown exception, in which case it terminates execution of the body of the try statement and all intervening bodies, and execution continues from the body of the catch clause.

8.2.10. Initialization of toplevel references

A toplevel reference has no associated frame. Instead, the lifecycle of its persistent value is associated with the loading and unloading of a module by the module runtime. The first time a toplevel reference is accessed following the loading of its containing module, its initializer expression is evaluated, and the resulting value is associated with the reference. This association survives until the toplevel reference is reassigned, or until the module is unloaded by the module runtime.

Initialization of a toplevel reference may result in recursive initialization of other toplevel references. Therefore, it is possible that a cycle could occur where evaluation of a toplevel reference occurs while evaluating its initializer expression. When this occurs, an InitializationError is thrown.

8.2.11. Initialization of late references

A reference annotated late may be uninitialized in a given frame. The rules of the language do not guarantee that an uninitialized late reference is never evaluated at runtime. If a late reference which is uninitialized in the current frame or current instance is evaluated, an InitializationError is thrown.

Likewise, if a non-variable late reference which is already initialized in the current frame or current instance is assigned, an InitializationError is thrown.

8.3. Execution of control structures and assertions

Control structures, as specified in §5.5 Control structures and assertions, are used to organize conditional and repetitive code within a body. Assertions are essentially a sophisticated sort of control directive, but for convenience are categorized together with control structures.

8.3.1. Evaluation of condition lists

Execution of an if, while, or assert requires evaluation of a condition list, as defined in §5.4 Conditions.

To determine if a condition list is satisfied, its constituent conditions are evaluated in the lexical order in which they occur in the condition list. If any condition is not satisfied, none of the subsequent conditions in the list are evaluated.

  • A boolean condition is satisfied if its expression evaluates to true when the condition is evaluated.

For any other kind of condition, the condition is satisfied if its value reference or expression evaluates to an instance of the required type when the condition is evaluated:

  • for an assignability condition, the condition is satisfied if the expression evaluates to an instance of the specified type when the control structure is executed,

  • for an existence condition, the condition is satisfied unless the expression evaluates to null when the control structure is executed, or

  • for a nonemptiness expression, the condition is satisfied unless the expression evaluates to an instance of []|Null when the control structure is executed.

A condition list is satisfied if and only if all of its constituent conditions are satisfied.

8.3.2. Validation of assertions

When an assertion, as specified in §5.5.6 Assertions, is executed, its condition list is evaluated. If the condition list is not satisfied, an exception of type AssertionError in ceylon.language is thrown.

The information carried by the AssertionError includes:

  • the text of the Ceylon code of the condition that failed,

  • the failure message, if any.

8.3.3. Execution of conditionals

The if/else and switch/case/else constructs control conditional execution.

When the if/else construct, specified in §5.5.1 if/else, is executed, its condition list is evaluated. If the condition list is satisfied, the if block is executed. Otherwise, the else block, if any, is executed, or, if the construct has an else if, the child if construct is executed.

When a switch/case/else construct, specified in §5.5.2 switch/case/else, is executed, its switch expression is evaluated to produce a value. The value is guaranteed to match at most one case of the switch. If it matches a certain case, then that case block is executed. Otherwise, switch is guaranteed to have an else, and so the else block is executed.

The value produced by the switch expression matches a case if either:

  • the case is a list of literal values and value references the value is identical to one of the value references in the list or equal to one of the literal values in the list, or if

  • the case is an assignability condition of form case (is V) and the value is an instance of V.

8.3.4. Execution of loops

The for/else and while loops control repeated execution.

When a while construct, specified in §5.5.4 while, is executed, the loop condition list is evaluated repeatedly until the first time the condition list is not satisfied, or until a break, return, or throw directive that terminates the loop is executed. Each time the condition is satisfied, the while block is executed.

When a for/else construct, specified in §5.5.3 for/else, is executed:

  • the iterated expression is evaluated to produce an an instance of Iterable,

  • an Iterator is obtained by calling iterator() on the iterable object, and then

  • the for block is executed once for each value of produced by repeatedly invoking the next() method of the iterator, until the iterator produces the value finished, or until a break, return, or throw directive that terminates the loop is executed.

Note that:

  • if the iterated expression is also of type X[], the compiler is permitted to optimize away the use of Iterator, instead using indexed element access.

  • if the iterated expression is a range constructor expression, the compiler is permitted to optimize away creation of the Range, and generate the indices using the successor operation.

We say that the loop exits early if it ends via execution of a break, return, or throw directive. Otherwise, we say that the loop completes normally.

If the loop completes normally, the else block is executed. Otherwise, if the loop exits early, the else block is not executed.

8.3.5. Exception handling

When a try/catch/finally construct, specified in §5.5.5 try/catch/finally, is executed:

  • the resource expressions, if any, are evaluated in the order they occur, and then obtain() is called on each resulting resource instance of type Obtainable, in the same order, then

  • the try block is executed, then

  • destroy() is called on each resource instance of type Destroyable, and release() is called on each resource instance of type Obtainable, if any, in the reverse order that the resource expressions occur, passing the exception that propagated out of the try block, if any, then

  • if an exception did propagate out of the try block, the first catch block with a variable to which the exception is assignable, if any, is executed, and then

  • the finally block, if any, is executed, even in the case where an exception propagates out of the whole construct.

TODO: Specify what happens if close() throws an exception. (Same semantics as Java with "suppressed" exceptions.)

8.3.6. Dynamic type checking

Inside a dynamic block, a situation might occur that requires dynamic type checking, as specified in §5.3.5 Dynamic blocks. It is possible that:

  • the value to which an expression with no type evaluates at execution time might not be an instance of the type required where the expression occurs,

  • in particular, the value to which a switch expression with no type evaluates at execution time might be an instance of a type not covered by the cases of a switch with no else, or

  • a qualified or unqualified reference which does not refer to a statically typed declaration might not resolve to any declaration at all.

Whenever such a condition is encountered at runtime, an AssertionError is immediately thrown.

Note: in Ceylon 1.0, dynamic type checking is only supported on JavaScript virtual machines.

8.4. Evaluation, invocation, and assignment

Evaluation of an expression may result in:

  • invocation of a function or instantiation of a class,

  • evaluation of a value,

  • instantiation of an instance of Callable that packages a callable reference, or

  • assignment to a variable value.

8.4.1. Dynamic dispatch

Dynamic dispatch is the process of determing at runtime a member declaration based upon the runtime type of an object, which, as a result of subtype polymorphism, may be different to its static type known at compile time.

Any concrete class is guaranteed to have exactly one declaration of a member, either declared or inherited by the class, which refines all other declarations of the member declared or inherited by the class. At runtime, this member is selected.

There is one exception to this rule: member expressions where the receiver expression is of form super or (super of Type), as defined in §6.3.3 super, are dispatched based on the static type of the receiver expression:

  • Any invocation of a member of super is processed by the member defined or inherited by the supertype, bypassing any member declaration that refines this member declaration.

  • Any invocation of a member of an expression of form (super of Type) is processed by the member defined or inherited by Type, bypassing any member declaration that refines this member declaration.

8.4.2. Evaluation

Evaluation of a value reference, as defined in §6.5.4 Value references, produces its current value. Evaluation of a callable reference, as defined in §6.5.5 Callable references, that does not occur as the primary of a direct invocation results in a new instance of Callable that packages the callable reference.

person.name
'/'.equals

When a value reference expression is executed:

  • first, the receiver expression, if any, is evaluated to obtain a receiving instance for the evaluation, then

  • the actual declaration to be invoked is determined by considering the runtime type of the receiving instance, if any, and then

  • if the declaration is a reference, its persistent value is retrieved from the receiving instance, or

  • otherwise, execution of the calling body pauses while the body of its getter is executed by the receiving instance, then,

  • finally, when execution of the getter ends, execution of the calling body resumes.

The resulting value is the persistent value retrieved, or the return value of the getter, as specified by the return directive.

When a callable reference expression that does not occur as the primary of a direct invocation expression is executed:

  • first, the receiver expression, if any, is evaluated to obtain a receiving instance for the evaluation, then

  • the receiving instance, a reference to the declaration to be invoked, or a reference to the current frame or instance of every body that contains the referenced declaration are packaged together into an instance of Callable.

The resulting value is the instance of Callable. The concrete class of this instance is not specified here.

8.4.3. Assignment

Given a value reference, as defined in §6.5.4 Value references, to a variable, the assignment operator = assigns it a new value.

person.name = "Gavin"

When an assignment expression is executed:

  • first, the receiver expression of the value reference expression is executed to obtain the receiving instance, then

  • the actual declaration to be assigned is determined by considering the runtime type of the receiving instance, and then

  • if the member is a reference, its persistent value is updated in the receiving instance, or

  • otherwise, execution of the calling body pauses while the body of its setter is executed by the receiving instance with the assigned value, then,

  • finally, when execution of the setter ends, execution of the calling body resumes.

8.4.4. Invocation

Evaluation of an invocation expression, as defined in §6.6 Invocation expressions, results in invocation of a function or callable constructor, or instantiation of a class. Every invocation has a callable expression:

  • in a direct invocation, the callable expression is a callable reference, and

  • in an indirect invocation, the callable expression is an instance of Callable that packages an underlying callable reference.

In either case, the callable expression determines the instance and member to be invoked.

print("Hello world!")
Entry(person.name, person)

When an invocation expression is executed:

  • first, the callable expression is evaluated to obtain the receiving instance, then

  • each listed argument or spread argument is evaluated in turn in the calling body, and

  • if the argument list has a comprehension, a comprehension instance, as defined in §8.6 Evaluation of comprehensions, is obtained, and then

  • the actual declaration to be invoked is determined by considering the runtime type of the receiving instance, if any, and then

  • execution of the calling body pauses while the body of the function or initializer is executed by the receiving instance with the argument values, then

  • finally, when execution of the function or initializer ends, execution of the calling body resumes.

A function invocation evaluates to the return value of the function, as specified by the return directive. The argument values are passed to the parameters of the method, and the body of the method is executed.

A class instantiation evaluates to a new instance of the class. The argument values are passed to the initializer parameters of the class, and the initializer is executed.

8.4.5. Evaluation of anonymous functions

When an anonymous function expression, as defined in §6.4 Anonymous functions, is evaluated, a reference to the function and a reference to the current frame or instance of every containing body are packaged into an instance of Callable. The instance of Callable is the resulting value of the expression. The concrete class of this instance is not specified here.

8.4.6. Evaluation of enumerations

Evaluation of an enumeration expression, as defined in §6.6.12 Iterable and tuple enumeration, results in creation of an iterable stream or tuple.

{ "hello", "world" }
[ new, *elements ]

When an iterable enumeration expression is executed, a reference to the enumeration expression, together with a reference to the current frame or instance of every containing body, together with a comprehension instance, as defined in §8.6 Evaluation of comprehensions, in the case that the enumeration expression has a comprehension, are packaged together into a stream. Evaluation of an expression occurring in the enumeration expression occurs in the context of the packaged framed associated with the stream. When the stream is iterated, it produces, in turn:

  • one value for each listed argument, by evaluating the listed argument expression, and then

  • if the argument list has a spread argument, each value produced by the spread argument, or

  • if the argument list has a comprehension, each value produced by the comprehension instance, or

  • if there are no arguments, and no comprehension, the stream is empty and produces no values.

When a tuple enumeration expression is executed:

  • first, each listed argument or spread argument is evaluated in turn in the calling body, and

  • if the argument list has a comprehension, a comprehension instance, as defined in §8.6 Evaluation of comprehensions, is obtained, and then

  • the resulting argument values are packaged into an instance of Iterable or Sequence, and this object is the resulting value of the enumeration expression, unless

  • there are no arguments, and no comprehension, in which case the resulting value of the enumeration expression is the object empty.

In the case of an iterable enumeration, the concrete class of the resulting value is not specified here. In the case of a tuple enumeration it is always Tuple, Empty, or Sequence.

8.4.7. Evaluation of spread arguments and comprehensions

A spread argument, as defined in §6.6.5 Spread arguments, produces multiple values by iterating the iterable object to which the spread operator is applied.

When a spread argument expression type is a subtype of Sequential, the behavior does not depend upon where the spread argument occurs:

  • If it occurs as an argument, the sequence produced by evaluating the expression is passed directly to the parameter.

  • If it occurs in an enumeration expression, the sequence produced by evaluating the expression is appended directly to the resulting iterable object or tuple.

On the other hand, when a spread argument expression type is not a subtype of Sequential, the behavior depends upon where the spread argument occurs:

  • If it occurs as an argument to a variadic parameter in a positional argument list, the values produced by a spread argument are evaluated immediately and packaged into an instance of Sequence and passed to the variadic parameter, unless there are no values, in which case the object empty is passed to the variadic parameter.

  • If it occurs as an argument to a parameter of type Iterable at the end of a named argument list, the iterable object produced by evaluating the expression is passed directly to the parameter.

  • If it occurs in a tuple enumeration, the values produced by a spread argument are evaluated immediately and packaged into an instance of Sequence and appended to the resulting tuple.

  • If it occurs in an iterable enumeration, the iterable object produced by evaluating the expression is chained directly to the resulting iterable object.

Likewise, a comprehension, as defined in §6.6.6 Comprehensions, produces multiple values, as specified by §8.6 Evaluation of comprehensions. The behavior depends upon where the comprehension occurs:

  • If it occurs as an argument to a variadic parameter in a positional argument list, the values produced by the comprehension instance are evaluated immediately, packaged into an instance of Sequence, and passed to the variadic parameter, unless there are no values, in which case the object empty is passed to the variadic parameter.

  • If it occurs as an argument to a parameter of type Iterable at the end of a named argument list, the comprehension instance is packaged into an iterable object that produces the values of the comprehension on demand, and this iterable object is passed directly to the parameter. The concrete class of this object is not specified here.

  • If it occurs in a tuple enumeration, the values produced by the comprehension instance are evaluated immediately, packaged into an instance of Sequence, and appended to the resulting tuple.

  • If it occurs in an iterable enumeration, the comprehension instance is packaged into an iterable object that produces the values of the comprehension on demand, and this iterable object is chained directly to the resulting iterable object. The concrete class of this object is not specified here.

8.5. Operator expressions

Most operator expression are defined in terms of function invocation, value evaluation, or a combination of invocations and evaluations, as specified in §6.8 Operators. The semantics of evaluation of an operator expression therefore follows from the above definitions of evaluation and invocation and from its definition in terms of evaluation and invocation.

However, this specification allows the compiler to take advantage of the optimized support for primitive value types provided by the virtual machine environment.

8.5.1. Operator expression optimization

As a special exception to the rules, the compiler is permitted to optimize certain operations upon certain types in the module ceylon.language. These types are: Integer, Float, Character, Range, Entry, String, Array, and Tuple.

Thus, the tables in the previous chapter define semantics only. The compiler may emit bytecode or compiled JavaScript that produces the same value at runtime as the pseudo-code that defines the operator, without actually executing any invocation, for the following operators:

  • all arithmetic operators,

  • the comparison and equality operators ==, !=, <=>, <, >, <=, >= when the argument expression types are built-in numeric types, and

  • the Range and Entry construction operators .., :, and ->.

In all operator expressions, the arguments of the operator must be evaluated from left to right when the expression is executed. In certain cases, depending upon the definition of the operator, evaluation of the leftmost argument expression results in a value that causes the final value of the operator expression to be produced immediately without evaluation of the remaining argument expressions. Optimizations performed by the Ceylon compiler must not alter these behaviours.

Note: this restriction exists to ensure that any effects are not changed by the optimizations.

8.5.2. Numeric operations

The arithmetic operations defined in §6.8.10 Arithmetic operators for values of type Integer and Float are defined in terms of methods of the interface Numeric. However, these methods themselves make use of the native operations of the underlying virtual machine. Likewise, values of type Integer and Float are actually represented in terms of a format native to the virtual machine.

It follows that the precise behavior of numeric operations depends upon the virtual machine upon which the program executes. However, certain behaviours are common to supported virtual machines:

  • Values of type Float are represented according to the IEEE 754 specification, IEEE Standard for Binary Floating-Point Arithmetic, and floating point numeric operations conform to this specification. Where possible, a double-precision 64-bit representation is used. It is possible on both Java and JavaScript virtual machines.

  • Where possible, values of type Integer are represented in two's complement form using a fixed bit length. Where possible, a 64-bit representation is used. Overflow and underflow wrap silently. This is the case for the Java Virtual Machine.

  • Otherwise, values of type Integer are represented according to the IEEE 754 specification. This is the case for JavaScript virtual machines.

Platform-dependent behavior of numeric operations is defined in the Java Language Specification, and the ECMAScript Language Specification.

It might be argued that having platform-dependent behavior for numeric operations opens up the same portability concerns that affected languages like C in the past. However, the cross-platform virtual machines supported by Ceylon already provide a layer of indirection that substantially eases portability concerns. Of course, numeric code is not guaranteed to be completely portable between the Java and JavaScript virtual machines, but it's difficult to imagine how such a level of portability could reasonably be achieved.

8.6. Evaluation of comprehensions

When a comprehension, as specified in §6.6.6 Comprehensions, is evaluated, a reference to the comprehension, together with a reference to the current frame or instance of every containing body, are packaged together into a comprehension instance. A comprehension instance is not considered a value in the sense of §8.1 Object instances, identity, and reference passing. Instead, it is a stream of values, each produced by evaluating the expression clause of the comprehension.

A comprehension consists of a series of clauses. Each clause of a comprehension, except for the expression clause that terminates the list of clauses, produces a stream of frames. A frame is a set of values for iteration variables and condition variables declared by the clause and its parent clauses.

Note: each child clause can be viewed as a body nested inside the parent clause. The lifecycle of comprehension frames reflects this model.

Evaluation of an expression occurring in a comprehension clause occurs in the context of the packaged frames associated with the comprehension instance together with a comprehension frame associated with the clause.

8.6.1. for clause

The expression which produces the source stream for a child for clause may refer to an iteration variable of a parent for clause. In this case the child clause is considered correlated. Otherwise it is considered uncorrelated.

In either case, the child clause produces a stream of frames. For each frame produced by the parent clause, and for each value produced by the source stream of the child clause, the child clause produces a frame consisting of the parent clause frame extended by the iteration variable value defined by the child clause.

This comprehension has a correlated for clause. For each character c in each string w in words, the child for clause produces the frame { String word=w; Character char=c; }.

for (word in words) for (char in word) char

This comprehension has an uncorrelated for clause. For each string n in nouns, and each string a in adjectives, the child for clause produces the frame { String noun=n; String adj=a; }.

for (noun in nouns) for (adj in adjectives) adj + " " + noun

8.6.2. if clause

A child if clause filters its parent clause frames. For every frame produced by the parent clause which satisfies the condition list of the child clause, the child clause produces that frame, extended by any condition variable defined by the child clause.

This comprehension has an if clause. For each object o in objects that is a nonempty String, the if clause produces the frame { Object obj=o; String str=o; }.

for (obj in objects) if (is String str=obj, !str.empty) str

8.6.3. Expression clause

As specified in §6.6.6 Comprehensions, every comprehension ends in an expression clause. An expression clause produces a single value for each frame produced by its parent clause, by evaluating the expression in the frame. These resulting values are the values returned by the whole comprehension.

8.7. Concurrency

Neither this specification nor the module ceylon.language provide any facility to initiate or control concurrent execution of a program written in Ceylon. However, a Ceylon program executing on the Java Virtual Machine may interact with Java libraries (and other Ceyon modules) that make use of concurrency.

In this scenario, the execution of a Ceylon program is governed by the rules laid out by the Java programming language's execution model (Chapter 17 of the Java Language Specification). Ceylon references belonging to a class or interface are considered fields in the sense of the JLS. Any such refence not explicitly declared variable is considered a final field. Evaluation of a reference is considered a use operation, and assignment to or specification of a variable reference is considered an assign operation, again in terms of the JLS.