Skip to content

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
}
The class 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;
However, because objects are kept by reference, it is possible to declare an object of an abstract class, but initialize it with an object of a derived class:
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);
An abstract member function does not have a body.

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
}
A final member function must have a body. A class with one or more abstract member functions must be marked as an abstract class.

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>;
These arguments may be of any type. Within the body of the class, the type parameters may be used as though a type themselves:
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;
}
and variable declaration:
a:A;
The member variables of 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;
}
Arguments to these parameters must be given when an object of the class type is declared:
a:A(1);
The member variables of 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
}
Initialization parameters are used for simple object initialization, such as to set initial values and array sizes.

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
  }
}
The body of the operator should update the state of the object using the argument. There is no return value. For 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);
Such casts are by reference. For basic types, it is possible to declare implicit conversions by value, if an appropriate declaration has been made within the class body. To permit conversion to type C, for example:
class A {
  operator -> C {
    c:C;
    // do something
    return c;
  }
  ...
}
For 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.