Note: information on this page refers to Ceylon 1.0, not to the current release.
Type Parameters
Class, interface and function declarations may have one or more type parameters.
Usage
A simple type-parameterized (or generic) class might look like this:
class Class<out Element>(Element* elements) {
/* declarations of class members */
}
A simple type-parameterised (or generic) function might look like this:
void fun<First,Second>(First first, Second second) {
}
Note: By convention, type parameters are given meaningful names rather
than single letter names, such as T, U, V, etc, as is common in many
other languages.
Description
Type constructors
Conceptually, a parameterized class or interface declaration is a type constructor, that is, a function from types to types. It produces a type given a tuple of compatible type arguments.
Conceptually, a parameterized class or function declaration defines a function that produces the signature of an invokable operation given a tuple of compatible type arguments.
Note: we often view the definition of a generic class or function as a template for producing a class or function. However, this is a slightly misleading mental model. Generics in Ceylon are very different to C++-style templates, and are ultimately derived from the approach taken in languages like ML.
Type parameter lists
A type parameter list is a comma separated list of type names enclosed
in angle brackets (< and >). The type parameter list occurs directly
after the class, interface, or function name, and before any
value parameter list.
Variance
The type names in the type parameter list of a class, interface, or
function may optionally be preceded by a variance modifier
in (indicating a contravariant type parameter) or
out (indicating a covariant type parameter). Type parameters without
either modifier are invariant.
References to contravariant and covariant type parameters are only permitted in certain locations within the type-parameterised declaration. For example a covariant type parameter of a class is not permitted to occur in the parameter list of a method of the class. Likewise, a contravariant type parameter of the class is not permitted as the method's return type.
Constraints
Type-parameterized declarations may have a given clause for each declared
type parameter to constrain the permitted type argument.
The constraints are:
- An upper bound,
given X satisfies T, constrains arguments ofXto subtypes ofT. - An enumerated bound,
given X of T|U|V, constrains arguments ofXto be one of the enumerated types,T,U, orV.
Examples
The default supertype of a type parameter is
Anything,
so it's common to
see type constraints which use Object
as an upper bound if the declaration
doesn't support
Null.
An example of this is Set
from the language module:
shared interface Set<out Element>
satisfies Collection<Element> &
Cloneable<Set<Element>>
given Element satisfies Object {
// ...
}
Given this declaration it's not allowed to have a Set<String?>, because
String? means String|Null and although String satisfies Object,
Null does not.
Another example from the language module is Comparable,
declared like this:
shared interface Comparable<in Other> of Other
given Other satisfies Comparable<Other> {
// ...
}
This is an example of a self type bound: The type parameter Other is
constrained to itself be Comparable<Other>, loosely meaning that once the
type Comparable<Other> is instantiated, Other will be the same type as
the type instantiated type. Concretely, in the type instantiation
Comparable<Integer>, Other has the type Integer, and is thus a subtype
of Comparable<Integer>.
A final example is the language module's
sort() function, which
constrains the type parameter Element so that it can only be called
with a Comparable type argument:
shared Element[] sort<Element>({Element*} elements)
given Element satisfies Comparable<Element>
This is necessary so that sort() can use the <=> operator to determine
how two elements compare, and so that you cannot call sort() with elements
which cannot be compared.
Defaulted type parameters
Just as a parameter list can define defaulted parameters, a type argument list can define defaulted type parameters. Here's an example from the language module:
Iterable<out Element, out Absent=Null>
This means we can apply the type constructor
Iterable using either one
or two type arguments. If we supply only one type argument, the default type
(in this case Null) is used:
// same as Iterable<String, Null>
Iterable<String> zeroOrMore;
Iterable<String, Nothing> oneOrMore;
Using a defaulted type parameter can be used as a more flexible alternative to
a type alias. We could have declared Iterable
without a defaulted type parameter and used alises:
alias PossiblyEmpty<T> => Iterable<String, Null>
alias NonEmpty<T> => Iterable<String, Nothing>
(In fact, we don't need the aliases because Ceylon has built-in syntax sugar
for PossiblyEmpty<T> and NonEmpty<T>, the type abbreviations {T*} and
{T+}.)
See also
classdeclarationinterfacedeclarationfunctiondeclaration- Generic type parameters in the Ceylon language spec