Concrete definitions may be bound to slots via {...}. A
special form, <>
is used instead to indicate that a slot is
bound to a concrete definition only upon construction. It is used only
for ``stored'' functional slots that access a single value that must
be intialized upon construction.
Concrete slot definitions may contain the following constructs:
Computation is based on message passing. Objects are autonomous single-threaded computational agents that send messages listed within operations corresponding to messages from other objects. A message is a record that corresponds to a declared operation. ODL op declarations define records with names identical to operation names, and fields corresponding to argument lists. There are five phases in any act of message passing:
The simplest form of a concrete operation is a sequence of one-way message sends, for example, { a.m1(x); b.m2(y, z); ... }. Invocations are defined by by naming them and providing field values along with a recipient designation. Instead of recipient prefixing, ODL messages may also be invoked with an indication of a class of receivers, via className $ message. Stylistically, this form is useful for stateless services for which the identity of the recipient cannot matter. The run-time system is free to select any instance of the indicated class (or any subclass thereof) to receive the message. ODL does not prescribe a particular translation mechanism. Several are available. For example, because all occurrences of $ are statically determinable before execution, the system may construct a pool of such objects upon initialization and translate all invocations to normal prefixed form.
In the base syntax of procedural interaction, result-bearing operations define message records for returned values, and clients define corresponding operations to accept them:
class A op m1(x: T) : result(b: B) ... end end; class Auser ... op calla(a: A) { catch a.m1(x) op result(b: B) { b.m2(y, z); ... } end } end
Here, the sender enters a state in which its only next action is to receive a corresponding reply. A catch clause introduces one or more transiently available operations that accept replies from servers. Multiple named result messages and catches are also allowed. The names of the client operations must match those listed for the server. Translators must arrange that result operations be caught only when objects are in the required state.
A server object invokes these transient messages by name:
class A op m1(x: T) : result(b: B) { ...; result(new B); } end;
The recipient of the reply is left implicit. This logically requires that sender identity be transmitted as an implicit argument in all procedural messages. However, in ODL the sender identity is not otherwise accessible to the server. (Unless, of course, a sender field is explicitly added to the operation signature and used in the desired ways.) A server may perform additional actions after issuing a reply.
More conventional looking procedural forms are also supported, via anonymous return messages. For example:
op a : i: int; op b : ()
This declares the result of a as an anonymous record with single field i and the result of b as an anonymous, fieldless record. Each anonymous record is considered to have a different name. Anonymous return messages are sent via reply:
class A2 op m1(x: T) : B { ...; reply b; } end; class Auser ... op calla2(a: A2) { local b :B := a.m1(x); b.m2(y, z); } end
Here, the anonymous catch may be elided, and the reply used directly in a procedural fashion.
Simple blocking procedural interaction is the only two-way protocol natively supported in ODL. Others may be defined through combinations of one-way sends and object constructions. For example, a future may be defined via the construction of a helper object to wait out a procedure.
Functional operations have a restricted form. They are defined as single expressions using the value expression sublanguage described in section 3.2.3. Translators are required to transform functional expressions into procedural computations (possibly involving new independent, unreachable objects) that cannot interfere with other operations and objects.
Stored functions are yet further restricted. They may be defined only
via <>
, indicating that a stored value be attached upon
construction, retrieved upon access, and possibly rebound in the
course of other operations. Stored links may also be qualified as
packed. This is a hint to the translator that the object
referenced by the slot should be embedded within the representation of
the host object.
The base form of stored values is restricted to link values, not other types. The reflects an underlying object model in which state varies only as a function of connections among objects. Other forms may be implemented with the help of instances of elementary predefined classes. However, ODL programmers are not required to do so themselves. Translators may mechanically reduce them to base form. A stored value denoting a non-link type may be translated to one holding a link to an instance of a predefined class, where value accesses are forwarded to these objects. Value rebindings may be translated either to link rebindings of new objects with the required initial values or to set operations on the exisiting objects, or any other technique, at the discretion of the translator. Bindings of the form l := null for opt slots are handled similarly.
ODL local operation invocations are not received as messages. They are sequentially executed in the course of performing other actions. To avoid the need for redundant declaration, a local version of each functional non-local operation is automatically constructed if not otherwise present. Local operations must be invoked without a recipient prefix. (In contrast non-local self-invocations must be prefixed with self.)
Local functions and procedures may in turn invoke others, and may be recursive. Standard procedural invocation rules and semantics apply. One-way local operations are also allowed. Invocations are interpreted as structured ``gotos'' in which control does not return to the calling operation. For this reason, procedural operations may not invoke local one-ways.
The execution state of objects is in general unbounded. The existence and value of representational bounds for particular classes and objects may be conservatively assessed via static analysis of local operations. When bounds are not discerned or discernable, ODL implementations may establish maximal per-object run-time size limits and handle overflow as a run-time error.
Every instantiable class declaration automatically results in the
definition of a corresponding new operation in class
System. The new operation has arguments corresponding to all
slots declared as <>
, and returns a unique link value
referencing an object of the indicated class. Implementations of
new (as well as delete) are not definable within ODL,
although provision of implementation-specific blob-based classes
and object layout rules may make them so.
Without qualification, the class's new operation may be invoked anywhere. The visibility of new may be controlled via a generator clause in a class declaration. A generator clause names the classes of entities that may invoke new for the class.
ODL message passing rules assume preservation of referential integrity. Objects that may still receive messages may not be deleted. This is best implemented using automatic storage management (garbage collection). However, a delete operation is also associated with each concrete class. Visibility is also controlled via generator.