Chapter 4. Declarations

Ceylon is a statically typed language. Classes, interfaces, functions, values and aliases must be declared before use. The declaration of a function or value must include an explicit type, or allow the type to be inferred. Static typing allows the compiler to detect many errors, including:

All declarations follow a general pattern:

Annotations
(keyword | Type) (TypeName | MemberName) TypeParameters? Parameters*
CaseTypes? ExtendedType? SatisfiedTypes?
TypeConstraints?
(Definition | ";")

A type parameter does not need an explicit declaration of this form unless it has constraints. In the case that it does have constraints, the constraint declaration does follow the general pattern.

This consistent pattern for declarations, together with the strict block structure of the language, makes Ceylon a highly regular language.

4.1. Compilation unit structure

A compilation unit is a text file, with the filename extension .ceylon.

Note: it is recommended that source file names contain only characters from the ASCII character set. This minimizes problems when transferring Ceylon source between operating systems.

There are three kinds of compilation unit:

  • A regular compilation unit contains a list of toplevel type, value, or function definitions.

  • A module descriptor, defined in §9.3.10 Module descriptors, contains a module declaration. The file must be named module.ceylon.

  • A package descriptor, defined in §9.3.9 Package descriptors, contains a package declaration. The file must be named package.ceylon.

Any compilation unit may begin with a list of imported types, values, and functions.

Import* (ModuleDescriptor | PackageDescriptor | Declaration*)

4.1.1. Toplevel and nested declarations

A toplevel declaration defines a type—a class or interface—or a type alias, or a function or value.

Declaration: FunctionValueDeclaration | TypeDeclaration | ParameterDeclaration
FunctionValueDeclaration: FunctionDeclaration | ValueDeclaration | SetterDeclaration
TypeDeclaration: ClassDeclaration | ObjectDeclaration | InterfaceDeclaration | TypeAliasDeclaration

A toplevel declaration is not polymorphic and so may not be annotated formal, default, or actual.

Note: in a future release of the language, we might relax this restriction and support package extension with toplevel member refinement. This can be viewed as a regularization of the language. The practical application is that it would make toplevel invocations and instantiations polymorphic, obviating the need for things like dependency injection.

Most toplevel declarations contain nested declarations.

Nested declarations are often mixed together with executable statements.

4.1.2. Packages

Each compilation unit belongs to exactly one package. Every toplevel declaration of the compilation unit also belongs directly to this package. The package is identified by the location of the text file on the file system, relative to a root source directory, as defined in §9.2 Source layout.

A package is a namespace. A full package name is a period-separated list of identifiers.

FullPackageName: PackageName ("." PackageName)*

Note: it is recommended that package names contain only lowercase characters and decimal digits from the ASCII character set.

There is also a default package whose name is empty. It is impossible to import declarations from this package.

Every package belongs to exactly one module, as specified in §9.3 Module architecture. The default package belongs to the default module.

4.2. Imports

Code in one compilation unit may refer to a toplevel declaration in another compilation unit in the same package without explicitly importing the declaration. It may refer to a declaration defined in a compilation unit in another package only if it explicitly imports the declaration using the import statement.

An import statement specifies the name of a package to import from, and a list of declarations to import from that package.

Import: "import" FullPackageName ImportElements

A toplevel import statement is an import statement that occurs at the beginning of a compilation unit. A local import statement is an import statement that occurs at the beginning of a block, as defined in §5.3 Blocks and statements, class body, as defined in §4.5 Classes, or interface body, as defined in §4.4 Interfaces.

An import statement may introduce names into a namespace:

  • A toplevel import statement may introduce names into the toplevel namespace of the compilation unit in which it occurs.

  • A local import statement may introduce names into the local namespace of block, class body, or interface body in which it occurs.

For a given package:

  • in each compilation unit, there may be at most one toplevel import statement that imports the package, and

  • in each block, class body, or interface body, there may be at most one local import statement that imports the package.

An import statement may import from a package if and only if:

  • the package belongs to the same module as the compilation unit containing the import statement, as specified by §9.2 Source layout, or

  • the package is declared shared in its package descriptor, as defined by §9.3.9 Package descriptors, and the module descriptor of the module to which the compilation unit containing the import statement belongs, as specified by §9.2 Source layout, explicitly or implicitly imports the module containing the package, as defined by §9.3.10 Module descriptors.

Each import statement imports one or more toplevel declarations from the given package, specifying a list of import elements.

ImportElements: "{" (ImportElement ",")* (ImportElement | ImportWildcard) "}"
ImportElement: ImportTypeElement | ImportObjectElement | ImportFunctionValueElement

An import element is a reference to either:

  • a single toplevel type (a class, interface, or alias) of the package,

  • a single toplevel function or value of the package, or

  • all toplevel declarations of the package.

An import element belonging to a toplevel import may either:

  • introduce a name into the toplevel namespace of the compilation unit in which it occurs, or

  • result in an alias for a member of a type within the compilation unit in which it occurs.

Every import element belonging directly to a toplevel import statement introduces a name into the toplevel namespace of the compilation unit.

An import element belonging to a local import may either:

  • introduce a name into the namespace of the block, class body, or interface body in which it occurs, or

  • result in an alias for a member of a type within the block, class body, or interface body in which it occurs.

Every import element belonging directly to a local import statement introduces a name into the namespace of the block, class body, or interface body in which it occurs.

An import element may not refer to a declaration that is not visible to the compilation unit, as defined by §5.1.3 Visibility.

An import statement may not contain two import elements which refer to the same declaration.

Note that toplevel declarations in the package ceylon.language never need to be explicitly imported. They are implicitly imported by every compilation unit.

Note: the compiler produces a warning if an imported function or value hides, as defined in §5.1.4 Hidden declarations, any of the modifiers declared in ceylon.language listed in §7.4.1 Declaration modifiers, unless the modifier itself has an alias import in the compilation unit.

Note: an unused import results in a compiler warning.

4.2.1. Type imports

An import element that specifies a type name imports the toplevel type with that name from the imported package or type.

ImportTypeElement: TypeAlias? TypeName ImportElements?

The specified name must be the name of a type declaration belonging to the imported package or type.

import ceylon.collection { MutableSet, MutableList, MutableMap }

The import element may be followed by a list of nested import elements:

  • if the import element introduces a name into a namespace, and if a nested import element is a reference to a constructor, then the nested import element also introduces a name into the same namespace, and need not specify an alias, or, otherwise

  • the nested import element only defines an alias for the referenced member of the imported type, and this alias must be specified explicitly.

Note: an import element referring to a static member of a Java class imports the static member into the toplevel namespace of the compilation unit. However, this behavior is outside the scope of this specification.

4.2.2. Anonymous class imports

An import element that specifies the name of an anonymous class, as defined in §4.5.7 Anonymous classes, imports the anonymous class with that name from the imported package or type.

ImportObjectElement: FunctionValueAlias? MemberName ImportElements?

The specified name must be the name of an anonymous class declaration belonging to the imported package or type.

import ceylon.file { home, current }

The import element may be followed by a list of nested import elements:

  • if the import element introduces a name into a namespace, then a nested import element also introduces a name into the same namespace, and need not specify an alias, or, otherwise

  • the nested import element only defines an alias for the referenced member of the imported anonymous class, and this alias must be specified explicitly.

4.2.3. Function and value imports

An import element that specifies a function or value name imports the toplevel function or value with that name from the imported package or type.

ImportFunctionValueElement: FunctionValueAlias? MemberName

The specified name must be the name of a function or value declaration belonging to the imported package or type.

import ceylon.math.float { sqrt, e, pi }

4.2.4. Alias imports

The optional alias clause in a fully-explicit import allows resolution of cross-namespace declaration name collisions.

TypeAlias: TypeName "="
FunctionValueAlias: MemberName "="

An alias assigns a different name to the imported declaration, or to a member of the imported declaration. This name is visible within the compilation unit, block, class body, or interface body in which the import statement occurs.

import java.util { JavaMap = Map }
import my.math { fib = fibonnacciNumber }
import java.lang { 
    Math { sin, cos, ln=log }, 
    System { sysprops=properties },
    Char=Character { upper=toUpperCase, lower=toLowerCase, char=charValue } 
}

4.2.5. Wildcard imports

The elipsis ... acts as a wildcard in import statements. An import statement that specifies a wildcard imports every toplevel declaration of the imported package, except for any declaration whose name collides with the name of a declaration contained directly in the compilation unit, block, class body, or interface body in which the import statement occurs.

ImportWildcard: "..."

An import statement may specify a list of alias imports followed by a wildcard. In this case, the alias imports are imported with the specified names, and all other toplevel declarations are imported with their declared names.

import ceylon.collection { ... }
import my.math { fib = fibonnacciNumber, ... }

Note: overuse of wildcard imports is discouraged.

4.2.6. Imported name

Inside a compilation unit which imports a declaration, the declaration may be referred to, as specified in §5.1.7 Unqualified reference resolution and §5.1.8 Qualified reference resolution, by its imported name:

  • For an import element with an alias, the imported name is the alias.

  • For an import element with no alias, or for a wildcard import, the imported name is the original name of the declaration.

An import element may not result in an imported name that is the same as the name of a declaration directly contained in the compilation unit, block, class body, or interface body in which the import element occurs.

Two import elements occurring in the same compilation unit, block, class body, or interface body, which import into the toplevel namespace of the compilation unit, or into a local scope, may not result in the same imported name.

Two nested import elements belonging to the same import element may not result in the same imported name.

Note: if an imported declaration is already referenceable within a compilation unit without the import statement, for example, if it is defined in the same package, or in ceylon.language, then, even with the import statement, it is still referenceable via its declared name, as well as via the imported name.

4.3. Parameters

A function, class, or callable constructor declaration may declare parameters. A parameter is a value or function belonging to the declaration it parameterizes. Parameters are distinguished from other values or functions because they occur in a parameter list. A value or function is a parameter of a function, class, or constructor if it is:

  • declared inline in a parameter list of the function, class, or callable constructor, or

  • declared normally, within the body of the class, function, or callable constructor, but named in a parameter list of the class, function, or callable constructor.

A parameter list of a function, class, or constructor may have one or more elements without explicit type declarations. Each such element is interpreted as the name of a parameter declaration occurring in the body of the class, function, or constructor, and there must be a value or function declaration with that name. For a function, such an element is only allowed in the last parameter list of the function.

As a special exception, if a parameter of an anonymous function has no explicit type declaration, and there is no declaration with the given name occurring in the body of the anonymous function, then the type of the parameter must be inferable, according to §6.4.1 Anonymous function parameter type inference.

Conversely, every parameter declaration that occurs outside a parameter list must have the same name as a parameter with no explicit type that occurs in the parameter list of the function, class, or constructor in whose body the parameter declaration directly occurs, and its default argument, if any, must be specified in the parameter list.

A parameter declaration may only occur in a parameter list, or directly, as defined by §5.1 Block structure and references, in the body of a class, function, or callable constructor. A parameter declaration may not occur directly in the body of a getter, setter, or value constructor, nor in the body of a control structure. Nor may a parameter declaration appear as a toplevel declaration in a compilation unit.

ParameterDeclaration: (ValueParameter | CallableParameter | VariadicParameter) ";"

The following class definitions are semantically identical:

class Person(shared String name, shared variable Integer age=0, Address* addresses) {}
class Person(name, age=0, addresses) {
    shared String name;
    shared variable Integer age;
    Address* addresses;
}

4.3.1. Parameter lists

A parameter list is a list of parameter declarations and of names of parameters declared in the body of the class or function to which the parameter list belongs. A parameter list may include, optionally:

  • one or more required parameters,

  • one or more defaulted parameters (parameters with default values), and/or

  • a variadic parameter.

In a parameter list, defaulted parameters, if any, must occur after required parameters, if any. The variadic parameter, if any, must occur last.

Parameters: "(" ( (Required ",")* ( Required | (Defaulted ",")* (Defaulted | Variadic) ) )? ")"

Every parameter list has a type, which captures the types of the individual parameters in the list, whether they are defaulted, and whether the last parameter is variadic. This type is always an subtype of Anything[]. The type of an empty parameter list with no parameters is [].

A parameter may not be annotated formal, but it may be annotated default.

4.3.2. Required parameters

A required parameter is a value or callable parameter without a default argument.

A required parameter in a parameter list may be a parameter declaration, or the name of a non-variadic parameter declared in the body of the function or class.

Required: ValueParameter | CallableParameter | MemberName

Required parameters must occur before any other parameters in the parameter list.

4.3.3. Defaulted parameters

A defaulted parameter is a value or callable parameter that specifies an expression that produces a default argument. A defaulted parameter may be either:

  • a non-variadic parameter declaration, together with a default argument expression, or

  • the name of a non-variadic parameter declared in the body of the function or class, together with its default argument expression.

Defaulted: ValueParameter Specifier | CallableParameter LazySpecifier | MemberName Specifier

The = and => specifiers are used throughout the language. In a parameter list they are used to specify a default argument.

Specifier: "=" Expression
LazySpecifier: "=>" Expression

The default argument expression may involve other non-variable parameters declared earlier in the parameter list or lists. It may not involve parameters declared later in the parameter list or lists. It may not involve variable parameters of the parameter list.

The default argument expression may not involve an assignment, compound assignment, increment, or decrement operator.

Defaulted parameters must occur after required parameters in the parameter list.

(Product product, Integer quantity=1, Price pricing(Product p) => p.price)

A parameter of a method or class annotated actual may not specify a default argument. Instead, it inherits the default argument, if any, of the corresponding parameter of the method it refines.

If two parameter lists are almost identical, differing only in that the first parameter of one list is defaulted, and the first parameter of the second list is required, and P is the the type of the second parameter list, then the type of the first parameter list is []|P.

Note: in Ceylon 1.0, for a function with multiple parameter lists, defaulted parameters may only occur in the first parameter list. This restriction will be removed.

TODO: Should we, purely for consistency, let you write f(Float x) => x in a parameter list, when the callable parameter is declared in the body of the function or class?

4.3.4. Value parameters

A value parameter is a reference, as specified in §4.8.1 References, that is named or defined in a parameter list. Like any other value declaration, it has a name, type, and, optionally, annotations.

ValueParameter: Annotations ValueParameterPrefix MemberName
ValueParameterPrefix: Type | "value" | "dynamic"

A value parameter may be declared using the keyword dynamic in place of the parameter type, indicating that it is a partially typed declaration. Such a parameter has no type.

In general, a value parameter must have an explicit type declaration, and may not be declared with the keyword value.

As a special exception, if a parameter of an anonymous function is declared with the keyword value, then the type of the parameter must be inferable, according to §6.4.1 Anonymous function parameter type inference.

If a value parameter x has type X, and a parameter list has type P with the principal instantiation Sequential<Y>, then the type of a new parameter list formed by prepending x to the first parameter list is:

  • Tuple<X|Y,X,P>, or

  • []|Tuple<X|Y,X,P> if x is defaulted.

The default argument expression, if any, for a callable parameter is specified using an ordinary = specifier. The type of the default argument expression must be assignable to the declared type of the value parameter.

(String label, Anything() onClick)
({Value*} values, Comparison(Value,Value) by)

4.3.5. Callable parameters

A callable parameter is a function, as specified in §4.7 Functions, named or defined in a parameter list. Like any other function declaration, it has a name, type, one or more parameter lists, and, optionally, annotations.

CallableParameter: Annotations CallableParameterPrefix MemberName Parameters+
CallableParameterPrefix: Type | "void" | "dynamic"

A callable parameter may be declared using the keyword dynamic in place of the return type, indicating that it is a partially typed declaration. Such a parameter has no return type.

If a callable parameter f has callable type X(*A), as specified below in §4.7.1 Callable type of a function, and a parameter list has type P with the principal instantiation Sequential<Y>, then the type of a new parameter list formed by prepending f to the first parameter list is:

  • Tuple<Y|X(*A),X(*A),P>, or

  • []|Tuple<Y|X(*A),X(*A),P> if f is defaulted.

The default argument expression, if any, for a callable parameter is specified using a lazy => specifier. The type of the default argument expression must be assignable to the return type of the callable parameter.

(String label, void onClick())
({Value*} values, Comparison by(Value x, Value y))

4.3.6. Variadic parameters

A variadic parameter is a value parameter that accepts multiple arguments:

  • A variadic parameter declared T* accepts zero or more arguments of type T, and has type [T*].

  • A variadic parameter declared T+ accepts one or more arguments of type T, and has type [T+].

VariadicParameter: Annotations VariadicParameterPrefix MemberName
VariadicParameterPrefix: UnionType ("*" | "+")

A variadic parameter in a parameter list may be a variadic parameter declaration, or the name of a variadic parameter declared in the body of the function or class.

Variadic: VariadicParameter | MemberName

The variadic parameter must be the last parameter in a parameter list. A variadic parameter may not have a default argument. A variadic parameter declared T+ may not occur in a parameter list with defaulted parameters.

(Name name, Organization? org=null, Address* addresses)
(Float+ floats)

The type of a parameter list containing just a variadic parameter of type T* is [T*] The type of a parameter list containing just a variadic parameter of type T+ is [T+].

Note: in Ceylon 1.0, for a function with multiple parameter lists, a variadic parameters may only occur in the first parameter list. This restriction will be removed.

4.4. Interfaces

An interface is a type schema, together with implementation details for some members of the type. Interfaces may not be directly instantiated.

InterfaceDeclaration: Annotations InterfaceHeader (InterfaceBody | TypeSpecifier ";")

An interface declaration may optionally specify a list of type parameters. An interface declaration may also have a list of interfaces is satisfies, a self type or an enumerated list of cases, and/or a list of type constraints.

InterfaceHeader: ("interface"|"dynamic") TypeName TypeParameters? InterfaceInheritance TypeConstraints?
InterfaceInheritance: CaseTypes? SatisfiedTypes?

To obtain a concrete instance of an interface, it is necessary to define and instantiate a class that satisfies the interface, or define an anonymous class that satisfies the interface.

The body of an interface contains:

  • an optional list of local import statements,

  • member (method, attribute, and member class) declarations,

  • specification statements interpretable as attribute or method refinement, as defined in §4.8.7 Attribute refinement and §4.7.8 Method refinement, and

  • nested interface, type alias, and abstract class declarations.

InterfaceBody: "{" Import* (Declaration | Specification)* "}"

Unlike the body of a class, method, or attribute, the body of an interface is not executable, and does not directly contain procedural code.

shared interface Comparable<Other> {
    shared formal Comparison compare(Other other);
    shared Boolean largerThan(Other other) => compare(other)==larger;
    shared Boolean smallerThan(Other other) => compare(other)==smaller;
}

An interface may declare formal methods, attributes, and member classes, and concrete methods, getters, setters, and member classes. A reference declaration, as defined in §4.8.1 References, or anonymous class declaration, as defined in §4.5.7 Anonymous classes, may not directly occur in the body of an interface.

A non-abstract nested class declaration is called a member class of the interface. A nested interface or abstract class declaration is not part of the schema of the interface type, and is therefore not considered a member of the interface.

4.4.1. Interface bodies

The body of an interface consists purely of declarations. The following constructs may not occur sequentially in the body of an interface:

  • a statement or control structure (except for specification statements interpretable as attribute or method refinement),

  • a reference declaration,

  • a forward-declared method or attribute declaration, or

  • an object declaration.

Within an interface body, a super reference is any occurrence of the expression super, unless it also occurs in the body of a nested class or interface declaration. A statement or declaration contained in the interface body may not:

  • pass a super reference as an argument of an instantiation, function invocation, or extends clause expression or as the value of a value assignment or specification,

  • use a super reference as an operand of any operator except the member selection operator, or the of operator as specified in §6.3.3 super,

  • return a super reference, or

  • narrow the type of a super reference using an assignability condition, as defined in §5.4.2 Assignability conditions.

4.4.2. Interface inheritance

An interface may satisfy any number of other interfaces, as defined in §3.3.3 Satisfaction.

shared interface List<Element>
        satisfies Collection<Element> & Correspondence<Integer,Element>
        given Element satisfies Object {
    ...
}

Every type listed in the satisfies clause must be an interface. An interface may not satisfy the same interface twice (not even with distinct type arguments).

Note: this second restriction is not strictly necessary. In fact, satisfies List<One>&List<Two> means the same thing as satisfies List<One&Two>, and the compiler already needs to be able to figure that out when it comes to multiple instantiations of the same interface inherited indirectly. Still, the restriction seems harmless enough.

The interface is a subtype of every type listed in the satisfies clause. The interface is also a subtype of the type Object defined in ceylon.language.

An interface inherits all members (methods, attributes and member types) of every supertype. That is, every member of every supertype of the interface is also a member of the interface. Furthermore, the interface inherits all nested types (interfaces and abstract classes) of every supertype.

The schema of the inherited members is formed by substituting type arguments specified in the satisfies clause.

An interface that satisfies a nested interface must be a member of the type that declares the nested interface or of a subtype of the type that declares the nested interface.

A user-defined interface may not satisfy the interface Callable defined in ceylon.language nor directly satisfy the interface ConstrainedAnnotation defined in ceylon.language.

4.4.3. Sealed interfaces

A toplevel or nested interface may be annotated sealed and is called a sealed interface.

An interface annotated sealed may not be satisfied by a class or interface outside the module in which it is defined.

4.4.4. Enumerated interfaces

An interface declaration may enumerate a list of cases of the interface, as defined in §3.4.2 Cases.

shared interface Node<Element> 
            of Root<Element> | Branch<Element> | Leaf<Element> { ... }

The cases may be interfaces, classes, or toplevel anonymous classes. A case may be an abstract class. Each case must be a direct subtype of the interface type. An interface may not be a case of itself. An interface declaration may not list the same case twice.

If an interface has an of clause, then every interface or class which directly inherits the interface must occur as exactly one of the enumerated cases of the interface. Furthermore, any interface or class which indirectly inherits the interface must inherit exactly one of the enumerated cases of the interface.

4.4.5. Interface aliases

An interface alias is an interface declaration which specifies another type.

TypeSpecifier: "=>" Type

The specified type must be an interface type, that is, a reference to an interface with no type parameters or an instantiation of a generic interface, and is called the aliased type.

An interface alias simply assigns an alternative name to the aliased type. A reference to the alias may occur anywhere a reference to an interface may occur.

shared interface PeopleByName => Map<String,Person>;
interface Compare<Value> => Comparison(Value,Value);

If the aliased interface is a parameterized type, the aliased type must explicitly specify type arguments.

A class or interface may satisfy an interface alias, in which case, the class or interface inherits the aliased interface type.

Interface aliases are not reified types. The metamodel reference for an interface alias type—for example, PeopleByName—returns the metamodel object for the aliased interface—in this case, Map<String,Person>, as specified in §8.1.2 Type argument reification.

4.4.6. Dynamic interfaces

A dynamic interface is an interface declared with the keyword dynamic. Dynamic interfaces may be used to model the type of objects defined in dynamically typed native code.

Every declaration nested inside a dynamic interface must be declared formal. A dynamic interface may not satisfy any interface that is not also a dynamic interface.

Within a dynamic block, defined in §5.3.5 Dynamic blocks, assignment of a value with no Ceylon type to a dynamic interface type does not result in an AssertionError, as defined in §8.3.6 Dynamic type checking. Instead, the value is coerced to the dynamic interface type.

4.5. Classes

A class is a stateful, instantiable type. It is a type schema, together with implementation details of the members of the type.

ClassDeclaration: Annotations ClassHeader (ClassBody | ClassSpecifier ";")

An ordinary class declaration may specify, optionally, a list of parameters required to instantiate the type, and, also optionally, a list of type parameters. A class declaration may have a superclass, a list of interfaces it satisfies, a self type or an enumerated list of cases, and/or a list of type constraints.

ClassHeader: "class" TypeName TypeParameters? Parameters? ClassInheritance TypeConstraints?
ClassInheritance: CaseTypes? ExtendedType? SatisfiedTypes?

To obtain an instance of a class, it is necessary to instantiate the class, or a subclass of the class.

The body of a class contains:

  • an optional list of local import statements,

  • member (method, attribute, and member class) declarations,

  • nested interface, type alias, and abstract class declarations, and

  • instance initialization code and, if the class does not have a parameter list, constructors.

ClassBody: "{" Import* (Declaration | Statement | ConstructorDeclaration)* "}"

The body of a class may contain executable code.

shared class Counter(Integer initialCount=0) {
    
    variable Integer n = initialCount;
    
    print("Initial count: ``n``");
    
    shared Integer count => n;
    
    shared void increment() {
        n++;
        print("Count: ``n``");
    }
    
}

A non-abstract nested class declaration is called a member class of the class. A nested interface or abstract class declaration is not part of the schema of the class type, and is therefore not considered a member of the class.

A class is not required to have a separate nested constructor declaration. Instead, the body of the class may itself declare its initializer parameters. An initializer parameter may be used anywhere in the class body, including in method and attribute definitions.

shared class Key(Lock lock) {
    shared void lock() {
        lock.engage(this);
        print("Locked.");
    }
    shared void unlock() {
        lock.disengage(this);
        print("Unlocked.");
    }
    shared Boolean locked => lock.engaged;
}

An initializer parameter may be shared.

shared class Point(shared Float x, shared Float y) { ... }
shared class Counter(count=0) {
    shared variable Integer count;
    shared void increment() => count++;
}

If a class does not specify an initializer parameter list, it must have at least one shared constructor, as defined below in §4.9 Constructors.

4.5.1. Callable type of a class

The callable type of a class with an initializer parameter list captures the type and initializer parameter types of the class. The callable type is T(*P), where T is the applied type formed by the class with its own type parameters as type arguments, and P is the type of the initializer parameter list of the class.

The callable type of a class with a default constructor is the callable type of the default constructor.

A class with no initializer parameter list and no default constructor does not have a callable type.

An abstract class is not callable, except from the extends clause of a subclass, or the class specifier of a class alias.

4.5.2. Initializer section

The initial part of the body of a class is called the initializer of the class and contains a mix of declarations, statements, and control structures. The initializer is executed every time the class is instantiated. If the class does not have an initializer parameter list, the initializer section may include one or more constructor declarations, as defined in §4.9 Constructors.

A class initializer is responsible for initializing the state of the new instance of the class, before a reference to the new instance is available to clients.

shared abstract class Point() {
    shared formal Float x;
    shared formal Float y;
}
shared class DiagonalPoint(Float distance) 
        extends Point() {
    
    value d = distance / 2^0.5;
    x => d;
    y => d;
    
    "must have correct distance from origin" 
    assert (x^2 + y^2 == distance^2);
    
}
shared object origin 
        extends Point() {
    x => 0.0;
    y => 0.0;
}

Within a class initializer, a self reference to the instance being initialized is:

  • any occurrence of the expression this, as defined in §6.3.1 this, or super, as defined in §6.3.3 super, unless it also occurs in the body of a nested class or interface declaration,

  • any occurrence of the expression outer, as defined in §6.3.2 outer, in the body of a class or interface declaration immediately contained by the class,

  • any reference to an anononymous class that inherits the class, or, if the class is an anonymous class, to the anonymous class itself, or

  • any reference to a value constructor of the class or of a class which inherits the class.

A statement or declaration contained in the initializer of a class may not evaluate an attribute, invoke a method, or instantiate a member class upon the instance being initialized, including upon a self reference to the instance being initialized, if the attribute, method, or member class:

  • occurs later in the body of the class,

  • is annotated formal or default, or

  • is inherited from an interface or superclass, and is not refined by a declaration occurring earlier in the body of the class.

A member class contained in the initializer of a class may not extend a member or nested class of an interface or superclass of the class.

Furthermore, a statement or declaration contained in the initializer of a class may not:

  • pass a self reference to the instance being initialized as an argument of an instantiation, function invocation, or extends clause expression or as the value of a value assignment or specification,

  • use a self reference to the instance being initialized as an operand of any operator except the member selection operator, or the of operator,

  • return a self reference to the instance being initialized, or

  • narrow the type of a self reference to the instance being initialized using an assignability condition, as defined in §5.4.2 Assignability conditions.

Nor may the class pass a self reference to the instance being initialized as an argument of its own extends clause expression, if any.

As a special exception to these rules, a statement contained in an initializer may assign a self-reference to the instance being initialized to a reference annotated late.

For example, the following code fragments are not legal:

class Graph() {
    OpenList<Node> nodes = ArrayList<Node>();
    class Node() {
        nodes.add(this);    //error: self reference in initializer
    }
}
class Graph() {
    class Node() {}
    Node createNode() {
        Node node = Node();
        nodes.add(node);    //error: forward reference in initializer
        return node;
    }
    OpenList<Node> nodes = ArrayList<Node>();
}

But this code fragment is legal:

class Graph() {
    OpenList<Node> nodes = ArrayList<Node>();
    Node createNode() {
        Node node = Node();
        nodes.add(node);
        return node;
    }
    class Node() {}
}

4.5.3. Declaration section

The remainder of the body of the class consists purely of declarations, similar to the body of an interface. The following constructs may not occur sequentially in the declaration section:

However, the declarations in this second section may freely use this and super, and may invoke any method, evaluate any attribute, or instantiate any member class of the class or its superclasses.

Within the declaration section of a class body, a super reference is any occurrence of the expression super, unless it also occurs in the body of a nested class or interface declaration. A statement or declaration contained in the declaration section of a class body may not:

  • pass a super reference as an argument of an instantiation, function invocation, or extends clause expression or as the value of a value assignment or specification,

  • use a super reference as an operand of any operator except the member selection operator, or the of operator as specified in §6.3.3 super,

  • return a super reference, or

  • narrow the type of a super reference using an assignability condition, as defined in §5.4.2 Assignability conditions.

4.5.4. Class inheritance

A class may extend another class, as defined in §3.3.2 Extension.

shared class Customer(Name name, Organization? org = null) 
        extends Person(name, org) { 
    ... 
}

The class is a subtype of the type specified by the extends clause. If a class does not explicitly specify a superclass using extends, its superclass is the class Basic defined in ceylon.language.

A class may satisfy any number of interfaces, as defined in §3.3.3 Satisfaction.

class Token() 
        extends Datetime()
        satisfies Comparable<Token> & Identifier {
    ... 
}

The class is a subtype of every type listed in the satisfies clause. A class may not satisfy the same interface twice (not even with distinct type arguments).

A class inherits all members (methods, attributes, and member types) of every supertype. That is, every member of every supertype of the class is also a member of the class. Furthermore, the class inherits all nested types (interfaces and abstract classes) of every supertype.

Unless the class is declared abstract or formal, the class:

  • must declare or inherit a member that refines each formal member of every interface it satisfies directly or indirectly, and

  • must declare or inherit a member that refines each formal member of its superclass.

The schema of the inherited members is formed by substituting type arguments specified in the extends or satisfies clause.

A subclass with an initializer parameter list must pass arguments to each superclass initialization parameter or callable constructor parameter in the extends clause. A subclass with no initializer parameter list may not pass arguments in the extends clause.

shared class SpecialKey1()
        extends Key( SpecialLock() ) {
    ...
}
shared class SpecialKey2(Lock lock) 
        extends Key(lock) {
    ...
}

A subclass of a nested class must be a member of the type that declares the nested class or of a subtype of the type that declares the nested class. A class that satisfies a nested interface must be a member of the type that declares the nested interface or of a subtype of the type that declares the nested interface.

A user-defined class may not satisfy the interface Callable defined in ceylon.language nor directly satisfy the interface ConstrainedAnnotation defined in ceylon.language.

4.5.5. Abstract, final, sealed, formal, and default classes

A toplevel or nested class may be annotated abstract and is called an abstract class.

A toplevel or nested class may be annotated final and is called a final class.

A toplevel or nested class may be annotated sealed and is called a sealed class.

If a class annotated shared is a member of a containing class or interface, then the class may be annotated formal and is called a formal member class, or, sometimes, an abstract member class.

An abstract class or formal member class may have formal members.

An abstract class may not be instantiated.

A formal member class may be instantiated.

A class which is not annotated formal or abstract is called a concrete class.

A concrete class may not have formal members.

A class annotated final must be a concrete class.

A class annotated final may not have default members.

If a concrete class annotated shared is a member of a containing class or interface, then the class may be annotated default and is called a default member class.

A toplevel class may not be annotated formal or default.

An un-shared class may not be annotated formal or default.

A class annotated sealed may not be instantiated or extended outside the module in which it is defined.

A class with no parameter list may not be annotated sealed.

A member class annotated sealed formal must belong to a sealed class or interface.

Note: a formal member class would be a reasonable syntax for declaring virtual types. We think we don't need virtual types because they don't offer much that type parameters don't already provide. For example:

shared formal class Buffer(Character...) 
        satisfies Sequence<Character>;

4.5.6. Member class refinement

Member class refinement is a unique feature of Ceylon, akin to the "factory method" pattern of many other languages.

  • A member class annotated formal or default may be refined by any class or interface which is a subtype of the class or interface which declares the member class.

  • A member class annotated formal must be refined by every concrete class which is a subtype of the class or interface that declares the member class, unless the class inherits a concrete member class from a superclass that refines the formal member class.

A member class of a subtype refines a member class of a supertype if the member class of the supertype is shared and the two classes have the same name. The first class is called the refining class, and the second class is called the refined class.

Then, given the refined realization of the class it refines, as defined in §3.7.7 Realizations, and, after substituting the type parameters of the refined class for the type parameters of the refining class in the schema of the refining class, the refining class must:

  • have the same number of type parameters as the refined schema, and for each type parameter the intersection of its upper bounds must be a supertype of the intersection of the upper bounds of the corresponding type parameter of the realization,

  • have a parameter list with the same signature as the realization, and

  • directly or indirectly extend the class it refines.

Furthermore:

  • the refining class must be annotated actual, and

  • the refined class must be annotated formal or default.

If a member class is annotated actual, it must refine some member class of a supertype.

A member class may not, directly or indirectly, refine two different member classes not themselves annotated actual.

Then instantiation of the member class is polymorphic, and the actual subtype instantiated depends upon the concrete type of the containing class instance.

shared abstract class Reader() {
    shared formal class Buffer(Character* chars) 
            satisfies Character[] {}
    ...
}
shared class FileReader(File file) 
        extends Reader() {
    shared actual class Buffer(Character* chars) 
            extends super.Buffer(chars) {
        ...
    }
    ...
}

All of the above rules apply equally to member classes which are aliases.

shared abstract class Reader() {
    shared formal class Buffer(Character* chars) => AbstractBuffer(*chars);
    ...
}
shared class FileReader(File file) 
        extends Reader() {
    shared actual class Buffer(Character* chars) => FileBuffer(*chars);
    ...
}

4.5.7. Anonymous classes

An object or anonymous class declaration is a compact way to define a class with a single value constructor, together with a getter aliasing this value constructor.

ObjectDeclaration: Annotations ObjectHeader ClassBody

An object has an initial lowercase identifier. An object declaration does not specify parameters or type parameters.

ObjectHeader: "object" MemberName ObjectInheritance
ObjectInheritance: ExtendedType? SatisfiedTypes?

An object declaration specifies the name of the value and the schema, supertypes, and implementation of the class. It does not explicitly specify a type name. Instead, the type name is formed by prefixing the value name with \I, turning it into an initial uppercase identifier, as specified by §2.3 Identifiers and keywords.

An object class:

  • satisfies and/or extends the types specified by the object declaration,

  • has no initializer parameter list,

  • has a single shared value constructor with the same name as the object, with an empty body and the same extends clause as the object declaration, which is the single enumerated case of the class,

  • is shared, if and only if the object is annotated shared,

  • is neither abstract nor formal,

  • is implicitly final.

Therefore, members of an object may not be declared formal nor default.

The body of the object declaration is the body of the class.

This class never appears in types inferred by local declaration type inference or generic type argument inference. Instead, occurrences of the class are replaced with the intersection of the extended type with all satisfied types.

An object value is a getter, as defined in §4.8.2 Getters, that simply returns a reference to the value constructor of the class. The value:

  • is shared, if and only if the object is annotated shared,

  • may refine a member of a supertype, if and only if the object is annotated actual, and

  • is neither default nor formal.

Therefore, the object may not be annotated default nor formal.

Annotations applying to an object declaration are considered annotations of the object value, and are accessible via its ValueDeclaration, as defined in §6.10 Reference expressions.

The following declaration:

shared my object red extends Color('FF0000') {
     string => "Red";
}

Is exactly equivalent to:

shared final class \Ired of red extends Color {
     shared new red extends Color('FF0000') {}
     string => "Red";
}

shared my \Ired red => \Ired.red;

Where \Ired is the type name assigned by the compiler.

shared object sql {
    shared String escape(String string) { ... }
}

...

String escapedSearchString = sql.escape(searchString);

4.5.8. Enumerated classes

A class declaration may enumerate a list of cases of the class, as defined in §3.4.2 Cases.

  • For an abstract class, the cases may be classes or toplevel anonymous classes. Each case must be a direct subclass of the enumerated class. A case may itself be an abstract class.

  • For a non-abstract toplevel class, the cases must be value constructors of the class.

The cases listed in the of clause must exhaust every means by which an instance of the class may be instantiated:

  • if an abstract class has an of clause, then every class that directly extends the class must occur as exactly one of the enumerated cases of the class listed in the of clause and, furthermore, every class that indirectly inherits the abstract class must inherit one of the enumerated cases of the class, or

  • if a non-abstract class has an of clause, then every non-partial constructor of the class must occur as exactly one of the enumerated cases of the class listed in the of clause.

shared abstract class Boolean() 
        of true | false {}
        
shared object true extends Boolean() { string => "true"; }
shared object false extends Boolean() { string => "false"; }
shared abstract class Node<Element>(String name) 
        of Branch<Element> | Leaf<Element> { ... }
        
shared class Leaf<Element>(String name, Element element) 
        extends Node<Element>(name) { ... }
        
shared class Branch<Element>(String name, Node<Element> left, Node<Element> right) 
        extends Node<Element>(name) { ... }
shared class Status of enabled | disabled {
    shared actual String string;
    shared new enabled { string => "enabled"; }
    shared new disabled { string => "disabled"; }
}

A non-abstract class with an initializer parameter list or a callable constructor may not specify an of clause.

A non-abstract, non-toplevel class may not specify an of clause.

A class declaration may not list the same case twice.

Note: in a future release of the language, we may introduce an abbreviated syntax like:

shared abstract class Boolean(shared actual String string) 
        of object true ("true") | 
           object false ("false") {}

4.5.9. Class aliases

A class alias is a class declaration which specifies a reference to a class or callable constructor of a class, followed by a positional argument list, as defined in §6.6.7 Positional argument lists.

ClassSpecifier: "=>" (Extension | Construction)

The specification of the class or callable constructor is treated as a value expression, as in §3.3.2 Extension. The type of this value expression must be a class type, that is, a reference to a class with no type parameters or an instantiation of a generic class, and is called the aliased type.

A class alias simply assigns an alternative name to the aliased type. A reference to the alias may occur anywhere a reference to a class may occur.

shared class People(Person* people) => ArrayList<Person>(*people);
class Named<Value>(String name, Value val) 
        given Value satisfies Object
        => Entry<String,Value>(name, val);

Arguments to the initializer parameters of the aliased class must be specified.

If the aliased class is a parameterized type, the aliased type must explicitly specify type arguments.

The type arguments may not be inferred from the initializer arguments.

Note: currently the compiler imposes a restriction that the callable type of the aliased class must be assignable to the callable type of the class alias. This restriction will be removed in future.

If a toplevel class alias or un-shared class alias aliases an abstract class, the alias must be annotated abstract, and it may not be directly instantiated.

If a shared class alias nested inside the body of a class or interface aliases an abstract class, the alias must be annotated abstract or formal. If it is annotated formal, it is considered a member class of the containing class or interface. If it is annotated abstract, it is considered an abstract nested class of the containing class or interface.

A class alias may not alias a partial constructor. A shared class alias may not alias an un-shared constructor.

A class may extend a class alias, in which case, the class inherits the aliased class type.

Class aliases are not reified types. The metamodel reference for a class alias type—for example, People—returns the metamodel object for the aliased class—in this case, ArrayList<Person>, as specified in §8.1.2 Type argument reification.

4.6. Type aliases

A type alias declaration assigns a name to an arbitrary type expression, usually involving a union and/or intersection of types.

TypeAliasDeclaration: Annotations AliasHeader TypeSpecifier ";"
AliasHeader: "alias" TypeName TypeParameters? TypeConstraints?

The specified type may be any kind of type. A reference to the alias may be used anywhere a union or intersection type may be used. The alias may not appear in an extends or satisfies clause. The alias may not be instantiated.

shared alias Number => Integer|Float|Decimal|Whole;
alias ListLike<Value> => List<Value>|Map<Integer,Value>;
alias Numbered<Num,Value> given Num satisfies Ordinal<Num> 
        => Correspondence<Num,Value>;

Note: class, interface, and type aliases use a "fat arrow" lazy specifier => instead of = because the type parameters declared on the left of the specifier are in scope on the right of the specifier. An alias is in general a type constructor.

A class or interface may not extend or satisfy a type alias.

Type aliases are not reified types. The metamodel reference for a type alias type—for example, Number—returns the metamodel object for the aliased type—in this case, Integer|Float|Decimal|Whole, as specified in §8.1.2 Type argument reification.

4.7. Functions

A function is a callable block of code. A function may have parameters and may return a value. If a function belongs to a type, it is called a method.

FunctionDeclaration: Annotations FunctionHeader (Block | LazySpecifier? ";")

All function declarations specify the function name, one or more parameter lists, and, optionally, a list of type parameters. A generic function declaration may have a list of type constraints.

FunctionHeader: FunctionPrefix MemberName TypeParameters? Parameters+ TypeConstraints?

A function declaration may specify a type, called the return type, to which the values the method returns are assignable, or it may specify that the function is a void function—a function which does not return a useful value, and only useful for its effect.

FunctionPrefix: Type | "function" | "dynamic" | "void"

Instead of an explicit return type, a function may be declared using:

  • the keyword dynamic, indicating that it is a partially typed declaration with no return type, or

  • the keyword function, indicating that its return type is inferred.

A function implementation may be specified using either:

  • a block of code, or

  • a lazy specifier.

If a function is a parameter, it must not specify any implementation.

The return type of a void function is considered to be Anything defined in ceylon.language.

Note: a void function with a concrete implementation returns the value null. However, since a void function may be a reference to a non-void function, or a method refined by a non-void function, this behavior can not be depended upon and is not implied by the semantics of void.

4.7.1. Callable type of a function

The callable type of a function captures the return type and parameter types of the function.

  • The callable type of a function with a single parameter list is R(*P) where R is the return type of the method, or Anything if the function is void, and P is the type of the parameter list.

  • The callable type of a function with multiple parameter lists is O(*P), where O is the callable type of a method produced by eliminating the first parameter list, and P is the type of the first parameter list of the function.

Note: this means that the callable type of a function lists the parameter lists in reverse order of the function declaration. A function C f(A a)(B b) has the callable type C(B)(A), not C(A)(B).

Note: the identification of void with Anything instead of Null or some other unit type will probably be contraversial. This approach allows a non-void method to refine a void method or a non-void function to be assigned to a void functional parameter. Thus, we avoid rejecting perfectly well-typed code.

4.7.2. Functions with blocks

A function implementation may be a block.

  • If the function is declared void, the block may not contain a return directive that specifies an expression.

  • Otherwise, every conditional execution path of the block must end in a return directive that specifies an expression assignable to the return type of the function, or in a throw directive, as specified in §5.3.6 Definite return.

shared Integer add(Integer x, Integer y) {
    return x + y;
}
shared void printAll(Object* objects) {
    for (obj in objects) {
        print(obj);
    }
}
shared void addEntry(Key->Item entry) {
    map.put(entry.key,entry.item);
}
shared Set<Element> singleton<Element>(Element element) 
        given Element satisfies Comparable<Element> {
    return TreeSet { element };
}

4.7.3. Functions with specifiers

Alternatively, a function implementation may be a lazy specifier, that is, an expression specified using =>. The type of the specified expression must be assignable to the return type of the function. In the case of a function declared void, the expression must be a legal statement, as defined by §5.3.1 Expression statements.

shared Integer add(Integer x, Integer y) => x + y;
shared void addEntry(Key->Item entry) => map.put(entry.key,entry.item);
shared Set<Element> singleton<Element>(Element element) 
            given Element satisfies Comparable<Element>
        => TreeSet { element };

4.7.4. Function return type inference

A non-void, un-shared function with a block or lazy specifier may be declared using the keyword function in place of the explicit return type declaration. Then the function return type is inferred:

  • if the function implementation is a lazy specifier, then the return type of the function is the type of the specified expression,

  • if the function implementation is a block, and the function contains no return directive, then the return type of the method is Nothing (this is the case where the method always terminates in a throw directive), or,

  • otherwise, the return type of the function is the union of all returned expression types of return directives of the method body.

This function has inferred return type Integer.

function add(Integer x, Integer y) => x + y;

This function has inferred return type Float|Integer.

function unit(Boolean floating) {
    if (floating) {
        return 1.0;
    }
    else {
        return 1;
    }
}

This function has inferred return type Nothing.

function die() {
    throw;
}

4.7.5. Forward declaration of functions

The declaration of a function may be separated from the specification of its implementation. If a function declaration does not have a lazy specifier, or a block, and is not annotated formal, and is not a parameter, it is a forward-declared function.

A forward-declared function may later be specified using a specification statement, as defined in §5.3.3 Specification statements. The specification statement for a forward-declared function may be:

  • a lazy specification statement with parameter lists of exactly the same types as the function, and a specified expression assignable to the declared type of the function, or

  • an ordinary specification statement with a specified expression assignable to the callable type of the function.

Comparison order(String x, String y);
if (reverseOrder) {
    order(String x, String y) => y<=>x;
}
else {
    order(String x, String y) => x<=>y;
}
Comparison format(Integer x);
switch (base)
case (decimal) {
    format = (Integer i) => i.string; 
}
case (binary) {
    format = formatBin;
}
case (hexadecimal) {
    format = formatHex;
}

Every forward-declared function must explicitly specify a type. It may not be declared using the keyword function.

A toplevel function may not be forward-declared. A method of an interface may not be forward-declared. A method annotated default may not be forward-declared.

If a shared method is forward-declared, its implementation must be definitely specified by all conditional paths in the class initializer.

4.7.6. Functions with multiple parameter lists

A function may declare multiple lists of parameters. A function with more than one parameter list returns instances of Callable in ceylon.language when invoked. Every function with multiple parameter lists is exactly equivalent to a function with a single parameter list that returns an anonymous function.

This function declaration:

Boolean greaterThan<Element>(Element val)(Element element)
        given Element satisfies Comparable<Element> => 
                element>val;

is equivalent to the following:

Boolean(Element) greaterThan<Element>(Element val)
        given Element satisfies Comparable<Element> => 
                (Element element) => element>val;

For a function with n parameter lists, there are n-1 inferred anonymous functions. The ith inferred function:

  • has a callable type formed by eliminating the first i parameter lists of the original declared function,

  • has the i+1th parameter list of the original declared function, and

  • if i<n, returns the i+1th inferred function, or

  • otherwise, if i==n, has the implementation of the original declared function.

Then the original function returns the first inferred anonymous function.

This method declaration:

function fullName(String firstName)(String middleName)(String lastName)
        => firstName + " " + middleName + " " + lastName;

Is equivalent to:

function fullName(String firstName) =>
        (String middleName) =>
                (String lastName) =>
                        firstName + " " + middleName + " " + lastName;

4.7.7. Formal and default methods

If a function declaration does not have a lazy specifier, or a block, and is annotated shared, and is a method of either:

  • an interface, or

  • a class annotated abstract or formal,

then the function declaration may be annotated formal, and is called a formal method, or, sometimes, an abstract method.

shared formal Item? get(Key key);

A method which is not annotated formal is called a concrete method.

If a concrete method is annotated shared, and is a member of a class or interface, then it may be annotated default and is called a default method.

shared default void writeLine(String line) {
    write(line);
    write("\n");
}

A method annotated formal may not specify an implementation (a lazy specifier, or a block).

A method annotated default must specify an implementation (a lazy specifier, or a block), and may not be forward-declared.

Every formal method must explicitly specify a type. It may not be declared using the keyword function.

A toplevel method may not be annotated formal or default.

An un-shared method may not be annotated formal or default.

4.7.8. Method refinement

Methods may be refined, just like in other object-oriented languages.

  • A class or interface may refine any formal or default method it inherits, unless it inherits a non-formal non-default method that refines the method.

  • A concrete class must refine every formal method it inherits, unless it inherits a non-formal method that refines the method.

A method of a subtype refines a method of a supertype if the method of the supertype is shared and the two methods have the same name. The first method is called the refining method, and the second method is called the refined method.

Then, given the refined realization of the method it refines, as defined in §3.7.7 Realizations, and, after substituting the type parameters of the refined method for the type parameters of the refining method in the schema of the refining method, the refining method must:

  • have the same number of type parameters as the refined schema, and for each type parameter the intersection of its upper bounds must be a supertype of the intersection of the upper bounds of the corresponding type parameter of the realization,

  • have the same number of parameter lists, with the same signatures, as the realization, and

  • have a return type that is assignable to the return type of the realization, or

  • if it has no return type, the refined method must also have no return type.

Note: in a future release of the language, we would like to support contravariant refinement of method parameter types.

Furthermore:

  • the refining method must be annotated actual, and

  • the refined method must be annotated formal or default.

If a method is annotated actual, it must refine some method defined by a supertype.

A method may not, directly or indirectly, refine two different methods not themselves annotated actual.

Then invocation of the method is polymorphic, and the actual method invoked depends upon the concrete type of the class instance.

shared abstract class AbstractSquareRooter() {
    shared formal Float squareRoot(Float x);
}
class ConcreteSquareRooter()
        extends AbstractSquareRooter() {
    shared actual Float squareRoot(Float x) => x^0.5;
}

Alternatively, a subtype may refine a method using a specification statement, as defined in §5.3.3 Specification statements. The specification statement must satisfy the requirements of §4.7.5 Forward declaration of functions above for specification of a forward-declared function.

class ConcreteSquareRooter()
        extends AbstractSquareRooter() {
    squareRoot(Float x) => x^0.5;
}

4.8. Values

There are two basic kinds of value:

  • A reference defines state. It has a persistent value, determined at the moment it is specified or assigned.

  • A getter defines how a value is evaluated. It is defined using a block or lazy specifier, which is executed every time the value is evaluated. A getter may have a matching setter.

If a value belongs to a type, it is called an attribute.

ValueDeclaration: Annotations ValueHeader (Block | (Specifier | LazySpecifier)? ";")

All value declarations specify the value name.

ValueHeader: ValuePrefix MemberName

A value declaration may specify a type.

ValuePrefix: Type | "value" | "dynamic"

Instead of an explicit return type, a value may be declared using:

  • the keyword dynamic, indicating that it is a partially typed declaration with no type, or

  • the keyword value, indicating that its type is inferred.

Note: syntactically a value declaration looks like a function declaration with zero parameter lists. It is often helpful, in thinking about the syntax and semantics of Ceylon, to take the perspective that a value is a function with zero parameter lists, or, alternatively, that a function is a value of type Callable.

A value may be variable, in which case it may be freely assigned using the assignment and compound assignment operators defined in §6.8 Operators. This is the case for a reference annotated variable, or for a getter with a matching setter.

4.8.1. References

The lifecycle and scope of the persistent value of a reference depends upon where the reference declaration occurs:

  • A toplevel reference represents global state associated with the lifecyle of a module, as defined by §8.2.10 Initialization of toplevel references.

  • A reference declared directly inside the body of a class represents a persistent value associated with every instance of the class, as defined by §8.2.3 Current instance of a class or interface. Repeated evaluation of the attribute of a particular instance of the class produces the same result until the attribute of the instance is assigned a new value.

  • A reference declared inside a block represents state associated with a frame, that is, with a particular execution of the containing block of code, as defined in §8.2.4 Current frame of a block.

The persistent value of a reference may be specified or initialized as part of the declaration of the reference, or via a later specification statement, as defined in §5.3.3 Specification statements, or assignment expression, as defined in §6.8 Operators, or, if it is a parameter, by an argument to an invocation expression, as defined in §6.6 Invocation expressions.

A reference annotated variable has a persistent value that can be assigned multiple times. A reference not annotated variable has a persistent value that can be specified exactly once and not subsequently modified.

variable Integer count = 0;
shared Decimal pi = calculatePi();
shared Integer[] evenDigits = [0,2,4,6,8];

A reference declaration may have a specifier which specifies its persistent value or, in the case of a variable reference, its initial persistent value. The type of the specified expression must be assignable to the type of the reference.

If the specified expression has no type, and the declaration occurs within a dynamic block, then the specification is not type-checked at compile time.

If a reference is a parameter, it must not specify a persistent value.

A reference belonging to a class may be annotated late, in which case the initializer of the class is not required to initialize its persistent value. Furthermore, a self-reference to an instance being initialized may be assigned to the reference.

A reference annotated late may not be initialized or assigned a value by the class initializer. A parameter may not be annotated late. A reference not belonging to a class may not be annotated late.

If a class declares or inherits a variable reference, it must (directly or indirectly) extend the class Basic defined in ceylon.language.

4.8.2. Getters

A getter implementation may be a block.

shared Float total {
    variable Float sum = 0.0;
    for (li in lineItems) {
        sum += li.amount;
    }
    return sum;
}

Every conditional execution path of the block must end in a return directive that specifies an expression assignable to the type of the value, or in a throw directive, as specified in §5.3.6 Definite return.

Alternatively, a getter implementation may be a lazy specifier, that is, an expression specified using =>. The type of the specified expression must be assignable to the type of the value.

Name name => Name(firstName, initial, lastName);

4.8.3. Setters

A setter defines how the value of a getter is assigned.

SetterDeclaration: Annotations "assign" MemberName (Block | LazySpecifier)

The name specified in a setter declaration must be the name of a matching getter that directly occurs earlier in the body containing the setter declaration. If a getter has a setter, we say that the value is variable.

Within the body of the setter, a value reference to the getter evaluates to the value being assigned.

A setter implementation may be a block. The block may not contain a return directive that specifies an expression.

shared String name { return join(firstName, lastName); }
assign name { firstName=first(name); lastName=last(name); }

Alternatively, a setter implementation may be a lazy specifier. The specified expression must be a legal statement.

shared String name => join(n[0], n[1]);
assign name => n = [first(name), last(name)];

A setter may not be annotated shared, default or actual. The visibility and refinement modifiers of an attribute with a setter are specified by annotating the matching getter.

4.8.4. Value type inference

An un-shared value with a block, specifier, or lazy specifier may be declared using the keyword value in place of the explicit type declaration. Then the value's type is inferred:

  • if the value is a reference with a specifier, then the type of the value is the type of the specified expression,

  • if the value is a getter, and the getter implementation is a lazy specifier, then the type of the value is the type of the specified expression,

  • if the value is a getter, and the getter implementation is a block, and the getter contains no return directive, then the type of the value is Nothing (this is the case where the getter always terminates in a throw directive), or

  • otherwise, the type of the value is the union of all returned expression types of return directives of the getter body.

value names = List<String>();
variable value count = 0;
value name => Name(firstName, initial, lastName);

4.8.5. Forward declaration of values

The declaration of a reference may be separated from the specification or initialization of its persistent value. The declaration of a getter may be separated from the specification of its implementation. If a value declaration does not have a specifier, lazy specifier, or a block, and is not annotated formal, it is a forward-declared value.

A forward-declared value may later be specified using a specification statement, as defined in §5.3.3 Specification statements.

  • The specification statement for a forward-declared getter is a lazy specification statement with no parameter list, and a specified expression assignable to the type of the value.

  • The specification statement for a forward-declared reference is an ordinary specification statement with a specified expression assignable to the type of the value.

String greeting;
switch (language)
case (en) {
    greeting = "Hello";
}
case (es) {
    greeting = "Hola";
}
else {
    throw LanguageNotSupported();
}
print(greeting);

Every forward-declared value must explicitly specify a type. It may not be declared using the keyword value.

A toplevel value may not be forward-declared. An attribute of an interface may not be forward-declared. An attribute annotated default may not be forward-declared.

A forward-declared getter may not have a setter.

If a shared value is forward-declared, its implementation must be definitely specified by all conditional paths in the class initializer.

4.8.6. Formal and default attributes

If a value declaration does not have a specifier, lazy specifier, or a block, and is annotated shared, and is a member of either:

  • an interface, or

  • a class annotated abstract or formal,

then the value declaration may be annotated formal, and is called a formal attribute, or, sometimes, an abstract attribute.

shared formal variable String firstName;

An attribute which is not annotated formal is called a concrete attribute.

If a concrete attribute is annotated shared, and is a member of a class or interface, then it may be annotated default and is called a default attribute.

shared default String greeting = "Hello";

An attribute annotated formal may not specify an implementation (a specifier, lazy specifier, or a block). Nor may there be a setter for a formal attribute.

An attribute annotated default must specify an implementation (a specifier, lazy specifier, or a block), and may not be forward-declared.

Every formal attribute must explicitly specify a type. It may not be declared using the keyword function.

A toplevel attribute may not be annotated formal or default.

An un-shared attribute may not be annotated formal or default.

4.8.7. Attribute refinement

Ceylon allows attributes to be refined, just like methods. This helps eliminate the need for Java-style getter and setter methods.

  • A class or interface may refine any formal or default attribute it inherits, unless it inherits a non-formal non-default attribute that refines the attribute.

  • A concrete class must refine every formal attribute it inherits, unless it inherits a non-formal attribute that refines the attribute.

Any non-variable attribute may be refined by a reference or getter. A variable attribute may be refined by a variable refernce or by a getter and setter pair.

TODO: are you allowed to refine a getter or setter without also refining its matching setter or getter?

An attribute of a subtype refines an attribute of a supertype if the attribute of the supertype is shared and the two attributes have the same name. The first attribute is called the refining attribute, and the second attribute is called the refined attribute.

Then, given the refined realization of the attribute it refines, as defined in §3.7.7 Realizations, the refining attribute must:

  • be variable, if the attribute it refines is variable, and

  • have exactly the same type as the realization, if the attribute it refines is variable,

  • have a type that is assignable to the type of the refined schema, if the attribute it refines is not variable, or

  • if it has no type, the refined attribute must also have no type.

Furthermore:

  • the refining attribute must be annotated actual, and

  • the refined attribute must be annotated formal or default.

If an attribute is annotated actual, it must refine some attribute defined by a supertype.

An attribute may not, directly or indirectly, refine two different attributes not themselves annotated actual.

A non-variable attribute may be refined by a variable attribute.

TODO: Is that really allowed? It could break the superclass. Should we say that you are allowed to do it when you refine an interface attribute, but not when you refine a superclass attribute?

Then evaluation and assignment of the attribute is polymorphic, and the actual attribute evaluated or assigned depends upon the concrete type of the class instance.

shared abstract class AbstractPi() {
    shared formal Float pi;
}
class ConcretePi() 
        extends AbstractPi() {
    shared actual Float pi = calculatePi();
}

Alternatively, a subtype may refine an attribute using a specification statement, as defined in §5.3.3 Specification statements. The specification statement must satisfy the requirements of §4.8.5 Forward declaration of values above for specification of a forward-declared attribute.

class ConcretePi() 
        extends AbstractPi() {
    pi = calculatePi();
}

4.9. Constructors

A constructor is a callable block of code that produces a new instance of the class to which the constructor belongs. Every constructor must occur directly in the initializer section of a class. A constructor may have parameters. Every constructor implementation is a block of code.

ConstructorDeclaration: Annotations ConstructorHeader Block

The are two basic kinds of constructor:

  • A callable constructor declaration specifies the constructor name, if any, and exactly one parameter list.

  • A value constructor declaration specifies just the constructor name.

Any constructor declaration may, optionally, have an extends clause.

A constructor name must be an initial lowercase identifier.

ConstructorHeader: ValueConstructorHeader | CallableConstructorHeader
CallableConstructorHeader: "new" MemberName? Parameters ExtendedType?
ValueConstructorHeader: "new" MemberName ExtendedType?

If two constructors belong to the same class, then the constructors must have distinct names. A class may have at most one constructor with no name.

If a constructor has no name, then the constructor is called the default constructor of the class to which it belongs. The default constructor is always a callable constructor.

Every default constructor must be annotated shared.

Note: from the point of view of a client, a class with a default constructor and no named constructors is indistinguishable from a class with an initializer parameter list.

shared class Point {
    shared Float x;
    shared Float y;
    
    shared new origin {
        x = 0.0;
        y = 0.0;
    }
    shared new cartesian(Float x, Float y) {
        this.x = x; 
        this.y = y;
    }
    shared new polar(Float r, Float theta) {
        this.x = r * cos(theta);
        this.y = r * sin(theta);
    }
    shared new (Float x, Float y) 
            extends cartesian(x, y) {}
    
    string => "(``x``, ``y``)";
}

A class with an initializer parameter list may not declare constructors.

A generic class may not declare value constructors.

A class nested directly inside an interface may not declare value constructors.

A member class annotated formal, default, or actual may not declare constructors.

Note: in a future release of the language, we might relax this restriction, and simply require that every actual class provide a constructor with the same signature as the constructor of its superclass.

A constructor annotated sealed may not be invoked outside the module in which it is defined.

4.9.1. Callable type of a constructor

For a callable constructor, the callable type of the constructor captures the type of the class, and parameter types of the constructor. The callable type is T(*P), where T is the applied type formed by the class with its own type parameters as type arguments, and P is the type of the parameter list of the constructor.

A constructor of an abstract class is not callable, except from the extends clause of a subclass, or the class specifier of a class alias.

A partial constructor is not callable, except from the extends clause of another constructor of the same class.

The type of a value constructor is simply T, where T is the class to which it belongs.

4.9.2. Partial constructors

A callable constructor annotated abstract is called a partial constructor.

A partial constructor may not be annotated shared.

A default constructor may not be annotated abstract.

A value constructor may not be annotated abstract.

4.9.3. Constructor delegation

Every constructor of any class which does not directly extend Basic defined in ceylon.language must explicitly delegate, as defined in §3.3.2 Extension, to either:

  • a different callable constructor of the same class, specifying arguments for the parameters of the constructor, or,

  • a callable constructor of its immediate superclass, specifying arguments for the parameters of the superclass constructor, if the superclass declares constructors, or, otherwise

  • the initializer of its immediate superclass, specifying arguments for the initializer parameters, if the superclass has an initializer parameter list.

If the constructor of a class which directly extends Basic does not have an extends clause, the constructor implicitly delegates to the initializer of Basic.