Classes
A class named A
is declared as:
class A {
// declare members
}
A variable of class type A
is then declared as usual:
a:A;
Inheritance
A class type named A
that inherits from a class type named B
is declared as:
class A < B {
// declare members
}
B
is referred to as the base class of A
. Conversely, A
is referred to as a derived class of B
.
A class may be marked abstract
to indicate that it cannot be instantiated:
abstract class A {
// declare members
}
A class may be marked final
to indicate that it cannot be inherited by another class:
final class B < A {
// declare members
}
An abstract class cannot be instantiated directly:
a:A;
b:B;
a:A <- b;
Member variables
Variable declarations that appear within the body of a class are member variables:
class A {
c:C;
d:D;
}
An object of the class type contains instantiations of these member variables, which may be accessed with the dot (.
) operator:
f(a.c);
f(a.d);
Member functions
Function declarations that appear within the body of a class are member functions:
class A {
function f(b:B, c:C) -> D {
// do something
}
}
These member functions can be called on an object of the class type, again accessed with the dot (.
) operator. For a:A
, b:B
, c:C
, d:D
:
d <- a.f(b, c);
The body of a member function may use any member variables of the object on which the member function is called. The keyword this
is used to explicitly refer to the object on which the member function is called. If the class has a base class, the keyword super
is also used to explicitly refer to the object on which the member function is called, but cast to the base class.
All member functions are virtual. To delegate a call to a member function of the base class, also use the super
keyword:
class A < B {
function f(c:C) {
super.f(c); // calls f(c:C) in class B
}
}
A member function may be marked abstract
to indicate that it must be overridden by a derived class if objects of that class are to be instantiated:
abstract function f(a:A, b:B);
A member function may be marked override
to indicate that it is intended to override a member function in a base class, producing an error if it does not:
override function f(a:A, b:B) {
// do something
}
Tip
While it is not required to use override
in order to override a member function, it is strongly recommended, and the compiler will issue a warning when it is not used. A member function that overrides another without specifying override
will hide member functions in the base class that have the same name, but different parameters. Typically this is not the intent.
A member function may be marked final
to indicate that it cannot be overridden by a derived class:
final function f(a:A, b:B) {
// do something
}
Generics
A class declaration may include parameters for generic types that are to be specified when the class is used. These are declared using angle brackets in the class declaration:
class A<T,U> {
// declare members
}
When a variable of this type is declared, arguments are specified for the generic types, also using angle brackets:
a:A<B,C>;
class A<T,U> {
t:T;
u:U;
function get() -> U {
return u;
}
}
A member function may also be generic. Such functions are non-virtual, that is, they are never overridden, even by member functions in a derived class with the same parameters.
Initialization
When an object of a class type is declared, its member variables are initialized according to the initial values given in the class body. For the class:
class A {
b:Integer <- 0;
c:Integer;
}
a:A;
a
are initialized such that a.b == 0
, while a.c
is uninitialized.
A class can be given initialization parameters, which may be used to initialize any member variables. These are given in parentheses (after any generic parameters, if used):
class A(d:Integer) {
b:Integer <- 0;
c:Integer <- d;
}
a:A(1);
a
are now initialized such that a.b == 0
, and a.c == 1
.
The declarations a:A;
and a:A();
are equivalent.
Initialization arguments can be passed onto the base class if required:
class A(d:Integer) < B(d) {
// declare members
}
Assignment
Tip
Recall that, for basic types, assignment is by value, while for class types, assignment is by reference.
Objects of class type A
may be assigned another object of basic type A
or an object of a derived type of A
; i.e. if a:A
and b:B
with A < B
, it is possible to assign b <- a
but not a <- b
.
Such assignments are by reference. Objects of class type A
may be assigned by value from a basic type if an appropriate declaration has been made within the class body. To permit assignment of type C
, for example:
class A {
operator <- c:C {
// do something
}
}
a:A
and c:C
, the assignment a <- c
would then be valid, even though C
is not a derived class of A
.
Conversion
Objects of class type A
may be implicitly cast to an object of any base class of A
; i.e. if a:A
and b:B
with A < B
, the object a
can be implicitly converted to an object of type B
, as in the following:
function f(b:B) {
// do something
}
a:A;
f(a);
C
, for example:
class A {
operator -> C {
c:C;
// do something
return c;
}
...
}
a:A
and c:C
, the following function call would then be valid, even though C
is not a base class of A
function f(c:C) {
// do something
}
a:A;
f(a);
Slicing
Objects of class type A
may be accessed using square brackets if an appropriate declaration has been made within the class body:
class A {
operator [j:J] -> D {
return d[j];
}
d:D[_];
}
This is called a slice operator. For a:A
and i:J
, using a[i]
would call the slice operator. Furthermore, the return value of a slice operator is by reference, which means it is possible to assign to the return value:
a[i] <- e;
Slice operators may have parameters of any type.