Note: information on this page refers to Ceylon 1.2, not to the current release.
Type Parameters
Class, interface and function declarations may have a type parameter list containing 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 ofX
to subtypes ofT
. - An enumerated bound,
given X of T|U|V
, constrains arguments ofX
to 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
class
declarationinterface
declarationfunction
declaration- Generic type parameters in the Ceylon language spec