Classes¶
Classes in ABS describe stateful behavior. All objects in ABS have
exactly one class; objects are created via the new expression.
Classes implement one or more interfaces. If a class implements more
than one interface, Type-check expressions and
Type-cast expressions are used to obtain a reference to the same
object typed with a different interface. If no explicit
implements clause is given, the class will extend the standard
library interface ABS.StdLib.Object (see Object). The
ABS.StdLib.Object interface does not specify any methods.
Note
Classes typically explicitly implement one or more
interfaces so that they can receive method calls, but the
run method makes it meaningful to have objects without
public methods; such objects cannot accept method calls but
can send method calls to other objects.
Note
For ease of reasoning and analysis, ABS methods differ only by name. It is an error for a class to implement two interfaces that both contain a method with the same name.
Classes have fields that define the state of objects of that class. All fields are private and can only be accessed from methods defined in the class. Fields are defined in two ways:
Field declarations in the body of the class define fields that get their initial value from their init expression.
Class parameters define fields that get their initial value from the
newexpression.
Classes have an optional init block, which is executed for each new
object before any other code. The init block cannot contain processor
release points (i.e., await or suspend), blocking expressions
(i.e., get, duration, using resources), or explicitly throw an
exception via the throw statement. It is mainly used for
complicated field initializations and can be omitted.
Classes have an optional run method (see Active classes).
Classes have an optional recovery block. In case an uncaught
exception occurs in a method, the exception is matched against the
patterns given in the recovery block, and the associated statement(s)
are executed. If the exception does not match any pattern in the
recovery block, or if the recovery block itself raises an exception,
the object is killed. Code in the recovery block has the same
restrictions as in the init block (i.e., no processor release points,
blocking expressions and throw statements).
ClassDecl ::= |
|
[ |
|
|
|
{ MethDecl } |
|
ClassParameters ::= |
Type SimpleIdentifier { |
InterfaceList ::= |
TypeIdentifier { |
TraitUse ::= |
|
FieldDeclList ::= |
{ Type SimpleIdentifier [ |
RecoveryBlock ::= |
|
MethDecl ::= |
Type SimpleIdentifier |
|
A class definition contains zero or more method definitions. Each method has a name, return type and zero or more parameters. All methods declared in an interface that is implemented by the class or one of their super-interfaces must be defined in the class body or in one of the used traits (see Traits). A class is free to define methods not declared in an interface; such methods are private to the class and cannot be called from outside the class.
Note
ABS currently does not support method overloading. Each method must have a unique name since methods are not disambiguated by their parameter lists.
Example:
class DataBase(Map<Filename,File> db) implements DB {
File getFile(Filename fId) {
return lookup(db, fId);
}
Int getLength(Filename fId){
return length(lookup(db, fId));
}
Unit storeFile(Filename fId, File file) {
db = insert(Pair(fId,file), db);
}
Filenames listFiles() {
return keys(db);
}
}
class Node(DB db, Peer admin, Filename file) implements Peer {
Catalog catalog;
List<Server> myNeighbors;
// implementation...
}
Active classes¶
A class can be active or passive. Active classes start an activity on their own upon creation. Passive classes only react to incoming method calls. A class is active if and only if it has a run method:
Unit run() {
// active behavior ...
}
The run method is asynchronously called after object initialization. Note that the run method does not need to terminate, but should periodically suspend itself so that the object can execute other methods.
Constant fields¶
Similar to variable declarations, field declarations and class
parameters can carry a Final annotation. the effect of such an
annotation is to forbid re-assignment to such a field.
The following example will lead to compile-time errors since we are
trying to assign new values to two fields declared as Final:
class Sample ([Final] Int constant_i) {
[Final] Int constant_j = 24;
Unit m() {
constant_i = 25; ①
constant_j = 24; ②
}
}
Final.Final.Read-only methods¶
For any method definition annotated with [Readonly], the compiler
will statically check that its body does not contain assignments to
fields.
If an interface declares a method to be read-only, its definition has
to be annotated with [Readonly] as well.
The following example shows a class declaration with a readonly method that will lead to a compile error:
class Wrong {
Int field = 12;
[Readonly] Unit fails() {
field = field + 1; ①
}
}
Readonly but
modifies a field.Atomic methods¶
For any method definition annotated with [Atomic], the compiler
will statically check that its body does not contain suspension points
(suspend and await statements) and blocking get
expressions. Such methods can be called inside init blocks and in
finally clauses; all other methods cannot be called in these
places.
If an interface declares a method to be atomic, its implementation has
to be annotated with [Atomic] as well.
The following example shows a call to an atomic method from an init
block. Removing the Atomic annotation from method m would
lead to a compile-time error:
class Sample {
Int field = 12;
{
field = this.m(); ①
}
[Atomic] Int m() {
return 24;
}
}
Atomic.