An adt,
or abstract data type, contains data objects and functions that operate on them. The syntax is
adt-declaration: identifier : adt {adt-member-listopt} adt-member-list: adt-member adt-member-list adt-member adt-member: identifier-list : cyclicopt data-type; identifier-list : function-type; identifier-list : con expression;After an adt-declaration, the identifier becomes the name of the type of that
adt
.
Point: adt { x, y : int; add : fn (p : Point, q : Point) : Point; eq : fn (p : Point, q : Point) : int; };the name
Point
is a type name of an adt
of two integers and two functions; the fragment:
r, s : Point; xcoord : int; ... xcoord = s.x; r = r.add(r, s);makes sense. The first assignment selects one of the data members of
s
; the second calls one of the function members of r
.
As this example indicates, adt
members are accessed by mentioning an object with the adt
type, a dot, and then the name of the member; the details will be discussed in Selection.
A special syntactic indulgence is available for functions declared within an adt
: frequently such a function receives as an argument the same object used to access it (that is, the object before the dot). In the example just above, r
was both the object being operated on and the first argument to the add
function. If the first formal argument of a function declared in an adt
is marked with the self
keyword, then in any calls to the function, the adt
object is implicitly passed to the function, and is not mentioned explicitly in the actual argument list at the call site. For example, in:
Rect: adt { min, max : Point; contains : fn (self Rect, Point) : int; }; r1 : Rect; p1 : Point; ... if (r1.contains (p1)) ...because the first argument of the
contains
function is declared with self
, the subsequent call to it automatically passes r1
as its first argument. The contains
function itself is defined elsewhere with this first argument explicit. (This mechanism is analogous to the this construct in C++ and other languages, but puts the special-casing at the declaration site and makes it explicit.)
If self
is specified in the declaration of a function, it also must be specified in the definition as well. For example, contains
would be defined:
Rect.contains (r : self Rect, p : Point) : int { . . . }Since abstract data types have value semantics, the
contains
function receives a copy of the adt
that is the implicit first argument and is unable to modify the original adt
. Should an adt specific function have to modify an adt, it should be written with a self ref
applied to the first argument type.
The adt
type in Limbo does not provide control over the visibility of its individual members; all members are accessible.
The cyclic
modifier is discussed in Forward Referencing <key> cyclic<kw> </key>.