Function, value, and class bodies contain procedural code that is executed when the function is invoked, the value evaluated, or the class instantiated. The code contains expressions and control directives and is organized using blocks and control structures.
Note: the Ceylon language has a recursive block structure—statements and declarations that are syntactically valid in the body of a toplevel declaration are, in general, also syntactically valid in the body of a nested declaration or of a control structure, and vice-versa.
A body is a block, class body or interface body. Every body is list of semicolon-delimited statements, control structures, and declarations, surrounded by braces. Some bodies end in a control directive. Every program element in the list is said to directly occur in the body. A program element directly occurs earlier than a second program element if both program elements directly occur in a body and the first program element occurs (lexically) earlier in the list than the second program element.
A program element (indirectly) occurs in a body if:
the program element directly occurs in the body, or
the program element indirectly occurs inside the body of a declaration or control structure that occurs directly in the body.
We sometimes say that the body contains the program element if the program element (indirectly) occurs in the body.
A program element (indirectly) occurs earlier than a second program element if:
the two program elements both directly occur in the same body, and the second program element occurs after the first program element, or
the second program element indirectly occurs inside the body of a declaration or control structure, and the first program element directly occurs earlier than the declaration or control structure.
The set of program elements that (indirectly) occur later than a program element is sometimes called the lexical scope of the program element.
A program element sequentially occurs in a body if:
the program element directly occurs in the body, or
the program element sequentially occurs inside the body of a control structure that occurs directly in the body.
A program element sequentially occurs earlier than a second program element if:
the two program elements both directly occur in the same body, and the second program element occurs after the first program element, or
the second program element sequentially occurs inside the body of a control structure, and the first program element directly occurs earlier than the declaration or control structure.
If a program element sequentially occurs earlier than a second program element, the sequence of statements from the first program element to the second program element comprises:
the sequence of statements that occur directly in the body in which the first program element directly occurs, beginning from the first program element and ending with the second program element, if the second program element occurs directly in the same body as the first program element, or
the sequence of statements that occur directly in the body in which the first program element directly occurs, beginning from the first program element and ending with the declaration or control structure in whose body the second program element sequentially occurs, followed by the sequence of statements from the first statement of the declaration whose body contains the second program element to the second program element itself, otherwise.
A package may not contain two toplevel declarations with the same name.
A program element is contained within the namespace of a declaration if either:
it directly occurs in the body in which the declaration directly occurs,
the declaration is a parameter or type parameter, and it directly occurs in the body of the parameterized declaration,
it is a control structure variable or iteration variable of a control structure contained in the namespace of the declaration, or
it directly occurs in the body of a control structure contained in the namespace of the declaration.
The namespace of a declaration may not contain a second declaration with the same name. (A parameter or type parameter is considered to be a declaration that is directly contained by the body of the declaration it parameterizes.)
A class or interface may not inherit a declaration with the same name as a declaration it contains unless either:
the contained declaration directly or indirectly refines the inherited declaration,
the contained declaration is a block local declaration, or
the inherited declaration is a block local declaration.
A class or interface may not inherit two declarations with the same name unless either:
both of the inherited declarations are formal and directly or indirectly refine some member of a common supertype,
the class or interface contains a declaration that directly or indirectly refines both the inherited declarations (in which case both the inherited declarations directly or indirectly refine some member of a common supertype),
one of the inherited declarations directly or indirectly refines the other inherited declaration, or
at least one of the inherited declarations is a block local declaration.
The scope of a declaration is governed by the body or package in which it occurs. A declaration is in scope at a program element if and only if either:
the declaration is a parameter or type parameter of a declaration whose body contains the program element,
the declaration is a control structure variable or iteration variable belonging to a block of a control structure that contains the program element,
the program element belongs to or is contained in the body of the declaration itself,
the program element belongs to or is contained in the body of a class or interface which inherits the declaration,
the declaration directly occurs in a body containing the program element,
the declaration is imported by the compilation unit containing the program element and is visible to the program element, or
the declaration is a toplevel declaration in the package containing the program element.
Where:
A control structure variable or iteration variable belongs to a block of a control structure if the block immediately follows the declaration of the variable.
A program element belongs to a declaration if it occurs in the extends, satisfies, of, given, abstracts, adapts or is clause of the declaration.
Furthermore:
a condition variable of a condition belonging to a condition list is in scope in any condition of the condition list that occurs lexically later, and
an iteration variable or condition variable of a comprehension is in scope in any clause of the comprehension that occurs lexically later.
If two declarations with the same name are both in scope at a certain program element, then one declaration may hide the other declaration.
If an inner body is contained (directly or indirectly) in an outer body, a declaration that is in scope in the inner body but is not in scope in the outer body hides a declaration that is in scope in the outer body. (In particular, a declaration inherited by a nested class or interface hides a declaration of the containing body.)
An block-local declaration of a class containing the program element hides a declaration inherited by the class.
An actual declaration hides the declaration it refines.
A declaration occurring in a body containing the program element hides a declaration imported by the compilation unit containing the body or implicitly imported from the module ceylon.language.
A toplevel declaration of the package containing the program element hides a declaration implicitly imported from the module ceylon.language.
A declaration explicitly imported by the compilation unit containing the program element hides a declaration implicitly imported from the module ceylon.language.
A declaration explicitly imported by the compilation unit containing the program element hides a toplevel declaration of the package containing the compilation unit.
A declaration explicity imported by name in the compilation unit containing the program element hides a declaration explicitly imported by wildcard in the compilation unit.
For example, the following code is legal:
class Person(name) {
String name;
shared String lowerCaseName {
String name = this.name.lowercased;
return name;
}
}As is this code:
class Point(x, y) {
shared Float x;
shared Float y;
}
class Complex(Float x, Float y=0.0)
extends Point(x, y) {}A declaration may be in scope at a program element, but not referenceable at the program element. A declaration is referenceable at a program element if the declaration is in scope at the program element and either:
the program element occurs within the lexical scope of the declaration, or
the declaration does not directly occur in a block or in the initializer section of a class body.
Note that these rules have very different consequences for:
declarations that occur in blocks and class initializer sections, and
toplevel declarations, and declarations that occur in class declaration sections and interface bodies.
Declarations that occurs in a block or class initializer section are interspersed with procedural code that initializes references. Therefore, a program element in a block or initializer may not refer to a declaration that occurs later in the block or class body. This restriction does not apply to declarations that occur in an interface body or class declaration section.
The following toplevel function declarations, belonging to the same package, are legal:
Float x => y;
Float y => x;
This code is not legal, since the body of a function is an ordinary block:
Float->Float xy() {
Float x => y; //compiler error: y is not referenceable
Float y => x;
return x->y;
}This code is not legal, since all three statements occur in the initializer section of the class body:
class Point() {
Float x => y; //compiler error: y is not referenceable
Float y => x;
Float->Float xy = x->y;
}However, this code is legal, since the statements occur in the declaration section of the class body:
class Point() {
Float x => y;
Float y => x;
}Likewise, this code is legal, since the statements occur in an interface body:
interface Point {
Float x => y;
Float y => x;
}A value declared using the keyword value or a function declared using the keyword function may be in scope at a program element, but its type may not be inferrable from the point of view of that program element.
The type of a value or function declared using the keyword value or function is inferrable to a program element if the declaration is in scope at the program element and the program element occurs within the lexical scope of the declaration.
Note: the type of a value or function declared using the keyword value or function is not inferrable within the body of the value or function itself.
For any other declaration, that is, for declarations which explicitly specify a type, the type is considered inferrable to a program element if the declaration is in scope at the program element.
The following code is not legal:
interface Point {
value x => y; //compiler error: type of y is not inferrable
value y => x;
}However, this code is legal:
interface Point {
value x => y;
Float y => x;
}An unqualified reference is:
the type name in an unqualified type declaration, for example String or Sequence in Sequence<String>,
the value name in an unqualified value reference, for example counter in counter.count, or
the function or type name in an unqualified callable reference, for example entries in entries(people*.name) or Entry in Entry(name,item).
If a program element contains an unqualified reference:
There must be at least one declaration with the given name in scope at the program element.
If multiple declarations with the given name are in scope at the program element where the given name occurs, then there must be exactly one such declaration which is not hidden by another declaration.
Then the given name refers to this unique unhidden declaration, and:
the declaration must be referenceable at the program element,
the type of the declaration must be inferrable to the program element, and
if the declaration is forward-declared, it must be definitely initialized at the program element.
As a special exception to the above, if there is no declaration with the given name in scope at the program element and the program element occurs inside a dynamic block, then the unqualified reference does not refer to any statically typed declaration.
A qualified reference is:
the type name in a qualified type declaration, for example Buffer in BufferedReader.Buffer,
the value name in a qualified value reference, for example count in counter.count, or
the function or type name in a qualified callable reference, for example split in text.split(), or Buffer in br.Buffer().
Every qualified reference has a qualifying type:
For a type declaration, the qualifying type is the full qualified type the qualifies the type name.
For a value reference or callable reference, the qualifying type is the type of the receiver expression.
If a program element contains a qualified reference:
The qualifying type must have at least one member with the given name which is visible at the program element.
If there are multiple visible members with the given name, then it is guaranteed by the type system that there is exactly one such member which is not refined by another member.
TODO: that's not true for intersection types! We need more detail here about member resolution for intersection and union types, including how we compute principal instantiations.
Then the given name refers to this unique member. If the program element is contained in the body of a class or interface, and the member declaration directly occurs in the body of the class or interface, and the qualified reference is a value reference or callable reference, and the receiver expression is a self reference to the instance being initialized, then:
the member declaration must be referenceable at the program element,
the type of the member must be inferrable to the program element, and
if the member declaration is forward-declared, it must be definitely initialized at the program element.
TODO: what about "inherited" interfaces and abstract classes? Currently the compiler allows this!
As a special exception to the above, if the program element occurs inside a dynamic block, and the the receiver expression has no type, then the qualified reference does not refer to any statically typed declaration.
The initializer of a class, or the body of a member of a class may instantiate, invoke, evaluate or assign members of the current instance of the class which defines the initializer or member—the instance being instantiated or invoked—without explicitly specifying the receiver, or by specifying a self reference this as the receiver.
The initializer of a nested class, or the body of a member of a nested class or nested interface may invoke, evaluate or assign members of the current instance of the containing class or interface—the parent instance of the instance being instantiated or invoked—without explicitly specifying the receiver, or by specifying an outer instance reference outer as the receiver.
A toplevel function or value body may not refer to a self reference, since there is no current instance.
The body of a value, function, or class nested inside the body of another function or value may invoke or evaluate any block local value or block local method whose declaration indirectly occurs earlier within the containing scope.
When a member of a class is hidden by a block local declaration, the member may be accessed via the self reference this or via the outer instance reference outer:
shared class Item(name) {
variable String name;
shared void changeName(String name) {
this.name = name;
}
}class Catalog(name) {
shared String name;
class Schema(name) {
shared String name;
Catalog catalog => outer;
String catalogName => outer.name;
class Table(name) {
shared String name;
Schema schema => outer;
String schemaName => outer.name;
String catalogName => catalog.name;
}
}
}When a toplevel declaration of a package is hidden by another declaration, the toplevel declaration may be accessed via the containing package reference package:
Integer n => 0; Integer f(Integer n) => n+package.n;
A block is list of semicolon-delimited statements, control structures, and declarations, surrounded by braces.
Block: "{" (Declaration | Statement)* "}"A statement is an assignment or specification, an invocation of a method, an instantiation of a class, a control structure, a control directive, or an assertion.
Statement: ExpressionStatement | Specification | Assertion | DirectiveStatement | ControlStructure
A statement or declaration contained in a block may not evaluate a value, invoke a function, instantiate a class, or extend a class whose declaration occurs later in the block.
Note: an unused block local declaration results in a compiler warning.
Only certain expressions are valid statements:
assignment,
prefix or postfix increment or decrement,
invocation of a method,
instantiation of a class.
ExpressionStatement: ( Assignment | IncrementOrDecrement | Invocation ) ";"
For example:
x += 1;
x++;
print("Hello");Main(process.arguments);
A control directive statement ends execution of the current block and forces the flow of execution to resume in some outer scope. They may only occur as the lexically last statement of a block.
DirectiveStatement: Directive ";"
There are four control directives:
Directive: Return | Throw | Break | Continue
For example:
throw Exception();
return x+y;
break;
continue;
The return directive must sequentially occur in the body of a function, getter, setter, or class initializer. In the case of a setter, class initializer, or void function, no expression may be specified. In the case of a getter or non-void function, an expression must be specified. The expression type must be assignable to the return type of the function or the type of the value. When the directive is executed, the expression is evaluated to determine the return value of the function or getter.
Return: "return" Expression?
The break directive must sequentially occur in the body of a loop.
Break: "break"
The continue directive must sequentially occur in the body of a loop.
Continue: "continue"
A throw directive may appear anywhere and may specify an expression, whose type must be a subtype of type Exception defined in ceylon.language. When the directive is executed, the expression is evaluated and the resulting exception is thrown. If no expression is specified, the directive is equivalent to throw Exception().
Throw: "throw" Expression?
A specification statement may specify or initialize the persistent value of a forward-declared reference, or specify the implementation of a forward-declared getter or function.
Specification: ValueSpecification | LazySpecification
The persistent value of a forward-declared reference or the implementation of a forward-declared function may be specified using =. The specification statement consists of an unqualified value reference and a specifier. The value reference must refer to a declaration which sequentially occurs earlier in the body in which the specification statement occurs.
ValueSpecification: MemberName Specifier ";"
The type of the specified expression must be assignable to the type of the reference, or to the callable type of the function.
String greeting;
if (exists name) {
greeting = "hello ``name``";
}
else {
greeting = "hello world";
}String process(String input);
if (normalize) {
process = String.normalized;
}
else {
process = (String s) => s;
}Note: there is an apparent ambiguity here. Is the statement x=1; a value specification statement, or an assignment expression statement? The language resolves this ambiguity by favoring the interpretation as a specification statement whenever that interpretation is viable. This is a transparent solution, since it accepts strictly more code than the alternative interpretation, and for ambiguous cases the actual semantics are identical between the two interpretations.
The implementation of forward-declared getter or function may be specified using =>. The specification statement consists of either:
an unqualified value reference and a lazy specifier, or
a unqualified callable reference, one or more parameter lists, and a lazy specifier.
The value reference or callable reference must refer to a declaration which sequentially occurs earlier in the body in which the specification statement occurs.
A callable reference followed by a parameter list is itself considered a callable reference, called a parameterized reference. If the parameter list has type P then the callable reference must have the exact type Callable<R,P> for some type R. Then the type of the parameterized reference is R.
ParameterizedReference: MemberName Parameters+
Thus, the specification statement consists of a parameterized reference followed by a lazy specifier.
LazySpecification: (MemberName | ParameterizedReference) LazySpecifier ";"
The type of the specified expression must be assignable to the type of the parameterized reference, or to the type of the value reference.
String greeting;
if (exists name) {
greeting => "hello ``name``";
}
else {
greeting => "hello world";
}String process(String input);
if (normalize) {
process(String input) => input.normalized;
}
else {
process(String s) => s;
}A sequence of statements may definitely return.
A sequence of statements definitely returns if it ends in a return or throw directive, or in a control structure that definitely returns.
A body definitely returns if it contains a list of statements that definitely returns.
An if conditional definitely returns if it has an else block and both the if and else blocks definitely return.
A switch conditional definitely returns if all case blocks definitely return and the else block, if any, definitely returns.
A for loop definitely returns if it has an else block that definitely returns, and there is no break directive in the for block, or if the iterated expression type is a nonempty type, and the for block definitely returns.
A while loop does not definitely return.
A try/catch exception manager definitely returns if the try block definitely returns and all catch blocks definitely return or if the finally block definitely returns.
The body of a non-void method or getter must definitely return.
A body may not contain an additional statement, control structure, or declaration following a sequence of statements that definitely returns. Such a statement, control structure, or declaration is considered unreachable.
A sequence of statements may definitely initialize a forward-declared declaration.
A sequence of statements definitely initializes a declaration if one of the statements is a specification statement or assigment expression for the declaration or a control structure that definitely initializes the declaration, or if the sequence of statements ends in a return or throw directive.
An if conditional definitely initializes a declaration if it has an else block and both the if and else blocks definitely initialize the declaration.
A switch conditional definitely initializes a declaration if all case blocks definitely initialize the declaration and the else block, if any, definitely initializes the declaration.
A for loop definitely initializes a declaration if it has an else block that definitely initializes the declaration, and there is no break directive in the for block, or if the iterated expression type is a nonempty type, and the for block definitely initializes the declaration.
A while loop does not definitely initialize a declaration.
A try/catch exception manager definitely initializes a declaration if the try block definitely initializes the declaration and all catch blocks definitely initialize the declaration or if the finally block definitely initializes the declaration.
A forward-declared declaration is considered definitely initialized at a certain statement or declaration if its declaration has a specifier or initializer, if it is referenced by a parameter, or if it is definitely initialized by the sequence of statements from its declaration to the given statement or declaration.
A forward-declared declaration must be definitely initialized wherever any value reference or callable reference to it occurs as an expression within the body in which it is declared.
A shared forward-declared declaration belonging to a class and not annotated late must be definitely initialized:
at every return statement of the initializer of the containing class, and
at the very last expression statement, directive statement or specification statement of the initializer of the containing class.
A specification statement for a method or non-variable reference, getter, or function may not (indirectly) occur in a for or while block unless the declaration itself occurs within the same for or while block.
A sequence of statements may possibly initialize a forward-declared declaration.
A sequence of statements possibly initializes a declaration if one of the statements is a specification statement for the declaration or a control structure that possibly initializes the declaration.
An if conditional possibly initializes a declaration if either the if block possibly initializes the declaration or if the else block, if any, possibly initializes the declaration.
A switch conditional possibly initializes a declaration if one of the case blocks possibly initializes the declaration or the else block, if any, possibly initializes the declaration.
A for loop possibly initializes a declaration if the for block possibly initializes the declaration or if it has an else block that possibly initializes the declaration.
A while loop possibly initializes a declaration if the while block possibly initializes the declaration.
A try/catch exception manager possibly initializes a declaration if the try block possibly initializes the declaration, if one of the catch blocks possibly initializes the declaration, or if the finally block possibly initializes the declaration.
A forward-declared declaration is considered definitely uninitialized at a certain statement or declaration if:
it is not possibly initialized by the sequence of statements from its declaration to the given statement or declaration,
the statement does not (indirectly) occur in the for block or else block of a for loop with a for block that possibly initializes it,
the statement does not (indirectly) occur in the while block of a while loop with a while block that possibly initializes it,
the statement does not (indirectly) occur in a catch block of a try/catch exception manager with a try block that possibly initializes it, and
the statement does not (indirectly) occur in the finally block of a try/catch exception manager with a try block or catch block that possibly initializes it.
A method or non-variable local or simple attribute must be definitely uninitialized wherever any value reference or callable reference to it occurs as a specification statement within the body in which it is declared.
Control of execution flow may be achieved using control directives and control structures. Control structures include conditionals, loops, and exception management.
Ceylon provides the following control structures:
the if/else conditional—for controlling execution based on a boolean condition, type condition, or check for a non-null or non-empty value,
the switch/case/else conditional—for controlling execution using an enumerated list of values or types,
the while loop—for loops which terminate based on a boolean condition, type condition, or check for a non-null or non-empty value,
the for/else loop—for looping over elements of an iterable object, and
the try/catch/finally exception manager—for managing exceptions and controlling the lifecycle of objects which require explicit destruction.
ControlStructure: IfElse | SwitchCaseElse | While | ForFail | TryCatchFinally | Dynamic
Control structures are not considered to be expressions, and therefore do not evaluate to a value. However, comprehensions—and conditional expressions, planned for a future version of the language—are part of the expression syntax and share much of the syntax and semantics of the control structures they resemble.
Assertions are runtime checks upon program invariants, or function preconditions and postconditions. An assertion failure represents a bug in the program, and is not considered recoverable. Therefore, assertions should not be used to control "normal" execution flow.
Note: of course, in certain circumstances, it is appropriate to handle the exception that results from an assertion failure, for example, to display a message to the user, or in a testing framework to aggregate and report the failures that occurred in test assertions. A test failure may be considered "normal" occurrence from the point of view of a testing framework, but it's not "normal" in the sense intended above.
Some control structures allow inline declaration of a variable, a reference that is scoped to the control structure body.
TypedVariable: Type MemberName
In most cases, the explicit type be omitted.
Variable: Type? MemberName
If the type is missing from the declaration, the type of the variables is inferred from the type of the expression that follows.
TODO: write down the rules for inferring the type of control structure variables!
A for loop requires an iteration variable declaration. An iteration variable declaration must specify an iteration variable.
IteratorVariable: Variable | CallableVariable | EntryVariablePair
An iteration variable of type Entry may be specified in destructured form.
EntryVariablePair: Variable "->" Variable
An iteration variable may be callable.
CallableVariable: (Type | "void")? MemberName Parameters+
If the return type is missing from the declaration, the return type of the callable variable is inferred.
TODO: is there any value to allowing this syntax? It seems more regular, but I really can't see why I would ever want to write for (f(Float x) in functions). Perhaps it could help readability in some cases?
Some control structures expect conditions. There are four kinds of condition:
a boolean condition is satisfied when a boolean expression evaluates to true,
an assignabilty condition is satisfied when an expression evaluates to an instance of a specified type,
an existence condition is satisfied when an expression evaluates to a non-null value, and
a nonemptiness condition is satisfied when an expression evaluates to a non-null, non-empty value.
Condition: BooleanCondition | IsCondition | ExistsOrNonemptyCondition
A condition list has one or more conditions.
ConditionList: "(" Condition ("," Condition) ")"A condition in the list may refer to a condition variable defined earlier in the list.
TODO: are we going to support satisfies conditions on type parameters, for example, if (Element satisfies Object), to allow refinement of its upper bounds?
A boolean condition is just an expression.
BooleanCondition: Expression
The expression must be of type Boolean.
An assignability, existence, or nonemptiness condition may contain either:
an unqualified value reference to a non-variable, non-default reference, or
an inline variable declaration together with an expression.
In the case of an assignability or existence condition, the type of the variable may be inferred.
IsCondition: "!"? "is" (TypedVariable Specifier | Type MemberName)
ExistsOrNonemptyCondition: ("exists" | "nonempty") (Variable Specifier | MemberName)The type of the value reference or expression must be:
in the case of an assignability condition, a type which is not a subtype of the specified type, but whose intersection with the specified type is not exactly Nothing, except
in the case of a negated assignability condition with !is, a type whose intersection with the specified type is not exactly Nothing, and which is not a supertype of the specified type, or
in the case of an exists condition, a type whose intersection with Null is not exactly Nothing and whose intersection with Object is not exactly Nothing, or
in the case of a nonemptiness condition, a subtype of Anything[]? whose intersection with [] is not exactly Nothing, and whose intersection with [Nothing+] is not exactly Nothing.
Note: an assignability condition may narrow to an intersection or union type.
if (is Printable&Identifiable obj) { ... }
if (is Integer|Float num) { ... }Every existence or nonemptiness condition is equivalent to—and may be considered an abbreviation of—an assignability condition:
exists x is equivalent to is Object x, and
nonempty x is equivalent to is [E+] x where x is an expression whose type is an invariant subtype of E[]?.
For an is condition:
if the condition contains a value reference, the value will be treated by the compiler as having type T&X where the conditional expression is of type T and X is the specified type, inside the block that immediately follows the condition, unless
it is a negated assignability condition with !is, in which case the value will be treated by the compiler as having type T~X.
TODO: define the ~ operation, which is in general not very robust due to decidability constraints.
If you prefer, you can think of the following:
Transaction tx = ...
if (is Usable tx) { ... }As an abbreviation of:
if (is Transaction&Usable tx = tx) { ... }Where the tx declared by the condition hides the outer declaration of tx inside the block that follows.
For an exists condition:
if the condition declares a variable, the declared type of the variable must be a supertype of T&Object, where the specifier expression is of type T, or
if the condition contains a value reference, the value will be treated by the compiler as having type T&Object inside the block that immediately follows the condition, where the conditional expression is of type T.
For a nonempty condition:
if the condition declares a variable, the declared type of the variable must be a supertype of T&[E+], where the specifier expression is of type T and T is an invariant subtype of E[]?, or
if the condition contains a value reference, the value will be treated by the compiler as having type T&[E+] inside the block that immediately follows the condition, where the conditional expression is of type T and T is an invariant subtype of E[]?.
If you prefer, you can think of the following:
if (exists name) { ... }As an abbreviation of:
if (exists String name = name) { ... }Where the name declared by the condition hides the outer declaration of name inside the block that follows.
The if/else conditional has the following form:
IfElse: If Else?
If: "if" ConditionList Block
Else: "else" (Block | IfElse)
The construct may include a chain of an arbitrary number of child else if clauses.
For example:
if (payment.amount <= account.balance) {
account.balance -= payment.amount;
payment.paid = true;
}
else {
throw NotEnoughMoneyException();
}shared void welcome(User? user) {
if (exists user) {
print("Welcome back, ``user.name``!");
}
else {
print("Welcome to Ceylon!");
}
}if (is CardPayment p = order.payment,
!p.paid) {
p.card.charge(total);
}The switch/case/else conditional has the following form:
SwitchCaseElse: Switch Cases
Switch: "switch" "(" Expression ")"Cases: CaseItem+ DefaultCaseItem?
CaseItem: "case" "(" Case ")" BlockDefaultCaseItem: "else" Block
For a switch statement whose switch expression is of type U, each case must be either:
a list of value references of form case (x, y, z) where each value reference x, y, and z refers to an anonymous class that is a subtype of U, or
an assignability condition of form case (is V) where the intersection type V&U is not exactly Nothing.
Case: Expression ("," Expression)* | "is" TypeTwo cases are said to be disjoint if the intersection of their types is exactly Nothing. In every switch statement, all cases must be mutually disjoint.
A switch is exhaustive if the union type formed from by the types of the cases of the switch covers the switch expression type.
If no else block is specified, the switch must be exhaustive.
Note: On the other hand, even if the switch is exhaustive, an else block may be specified, in order to allow a switch that accommodates additional cases without resulting in a compilation error.
Note: an assignability condition case may narrow to an intersection or union type.
case (is Format & IdentifiableObject) { ... }
case (is Integer | Float) { ... }If a switch has an assignability condition case, then the switch expression must be an unqualified value reference to a non-variable, non-default reference.
For an assignability condition case, the value referred by the switch expression will be treated by the compiler as having the intersection type of its declared type with the specified type inside the case block. This intersection type must not be exactly Nothing.
For example:
Boolean? maybe = ... ;
switch (maybe)
case (null, false) {
return false;
}
case (true) {
return true;
}Integer|Float number = ... ;
switch (number)
case (is Integer) {
return sqrt(number.float);
}
case (is Float) {
return sqrt(number);
}A Java-style overloaded method may be emulated as follows:
shared void print<Printable>(Printable printable)
given Value of String | Integer | Float {
switch (printable)
case (is String) {
print("\"``printable``\"");
}
case (is Integer) {
print(printable + ".00");
}
case (is Float) {
print(formatFloat(printable, 2));
}
}The for/else loop has the following form:
ForFail: For Fail?
For: "for" ForIterator Block
Fail: "else" Block
The for iterator consists of an iteration variable declaration and an iterated expression that contains the range of values to be iterated.
ForIterator: "(" IteratorVariable "in" Expression ")"The type of the iterated expression depends upon the iteration variable declarations:
The iterated expression must be an expression of type assignable to Iterable<X> where X is the declared type of the iteration variable.
If two iteration variables are defined, the iterated expression type must be assignable to Iterable<Entry<U,V>> where U and V are the declared types of the iteration variables.
For example:
for (p in people) {
print(p.name);
}variable Float sum = 0.0;
for (i in -10..10) {
sum += x[i] else 0.0;
}for (word -> freq in wordFrequencyMap) {
print("The frequency of ``word`` is ``freq``.");
}for (p in group) {
if (p.age >= 18) {
log.info("Found an adult: ``p.name``.");
break;
}
}
else {
log.info("No adult in group.");
}
The while loop has the form:
While: LoopCondition Block
The loop condition list determines when the loop terminates.
LoopCondition: "while" ConditionList
TODO: does while need a fail block? Python has it, but what is the real usecase?
For example:
variable Integer n=0;
variable [Integer*] seq = [];
while (n<=max) {
seq=seq.withTrailing(n);
n+=step(n);
}The try/catch/finally exception manager has the form:
TryCatchFinally: Try Catch* Finally?
Try: "try" ("(" Resource ")")? BlockCatch: "catch" "(" Variable ")" BlockFinally: "finally" Block
Each catch block defines a variable. The type of the variable must be assignable to Exception in ceylon.language. If no type is explicitly specified, the type is inferred to be Exception.
Note: a catch block type may be a union or intersection type:
catch (NotFoundException|DeletedException e) { ... }If there are multiple catch blocks in a certain control structure, then:
The type of a catch variable may not be a subtype of any catch variable of an earlier catch block belonging to the same control structure.
If the type of a catch variable is a union type E1|E2|...|En then no member Ei of the union may be a subtype of any catch variable of an earlier catch block belonging to the same control structure.
The try block may declare a resource expression, which may be either:
an unqualified value reference to a non-variable reference,
an instantiation expression, or
an inline variable declaration together with an expression.
A resource expresson produces a heavyweight object that must be released when execution of the try terminates. Each resource expression must be of type assignable to Closeable in ceylon.language.
Resource: MemberName | InitializerReference Arguments | Variable Specifier
For example:
try (file = File(path)) {
file.open(readOnly);
...
}
catch (FileNotFoundException fnfe) {
print("file not found: ``path``");
}
catch (FileReadException fre) {
print("could not read from file: ``path``");
}
finally {
assert (file.closed);
}try (Transaction()) {
try (s = Session()) {
return s.get(Person, id);
}
catch (NotFoundException|DeletedException e) {
return null;
}
}An assertion has an asserted condition list and, optionally, an annotation list.
Assertion: Annotations "assert" ConditionList ";"
The message carried by the assertion failure may be specified using a doc annotation.
"total must be less than well-defined bound" assert (exists bound, total<bound);
If the assertion contains an assignability, existence, or nonemptiness condition containing a value reference then the compiler treats the referenced value as having a narrowed type at program elements that occur in the lexical scope of the assertion.
{Element*} elements = ... ;
assert (nonempty elements);
Element first = elements.first;TODO: how can we support interpolation in the assertion failure message?
assert (total<bound) else "total must be less than ``bound``";
A dynamic block allows interoperation with dynamically typed native code.
Dynamic: "dynamic" Block
Inside a dynamic block, a value reference or qualified or unqualified reference occurring in an expression that does not resolve to any statically typed program element is assumed to be a reference to something defined in dynamically typed native code. Then the qualified or unqualified reference is not assigned a type, but does not result in a compile-time error.