In Limbo an abstract data type, ADT, is a user-defined type that encapsulates data and functions into a named type. It is similar to a C struct
, but can include functions, like a C++ class
. Like Limbo modules, however, an ADT is final and does not have inheritence or polymorphism.
ADT member declarations are much the same as data and function declarations elsewhere in a Limbo program.identifier
: adt {adt-member-list
};
For example, the following is a declaration of a simple Inventory
ADT:
Inventory: adt { id: string; onhand: int; cost: real; value: fn(item: Inventory): real; };This declaration contains three data members and one member function. Following the declaration,
Inventory
is a type, like int
or real
. Typically, ADT declarations are in a module file (.m
).
ADT member function definitions typically are in the implemenation file (.b
). ADT member function definitions are the same as regular Limbo function defintions, with the addition of the ADT qualifier in the name using the dot operator (.
).
Inventory.value(item: Inventory): real { return (real(item.onhand) * item.cost); }
part1: Inventory;This declares
part1
to be of type Inventory
.
.
) and can be treated like any other variable or function.
part1.id = "Widget"; part1.onhand = 250; part1.cost = 4.23;This assigns values to the
id
, onhand
and cost
members of part1
.
Calls to ADT member functions follow a similar pattern, for example:
sys->print("Value On Hand: $%5.2f\n", part1.value(part1));Using the values assigned above, this statement prints:
Value On Hand: $1057.50In this example, in order for the member function to operate on the member data, the instance of the ADT must be explicitly passed as a argument to the function. Passing of the ADT instance as an implicit argument can be done using the keyword
self
.
Inventory
member function to use the keyword self
passes an instance of the ADT as an implicit argument. For example, note the addition of the keyword self
to the value
function declaration and definition:
Inventory: adt { id: string; onhand: int; cost: real; value: fn(item: self Inventory): real; }; Inventory.value(item: self Inventory): real { return (real(item.onhand) * item.cost); }The addition of
self
to the member function declarations and definitions enables the calling of the member functions without explicitly passing the ADT instance. The following reflects the change to the statement that calls this function:
sys->print("Value On Hand: $%5.2f\n", part1.value());Using the values assigned earlier, this statement prints:
Value On Hand: $1057.50The keyword
self
enables similar implicit argument functionality as the this
keyword used in C++ and Java.
ref
keyword.
For example, to instantiate the Inventory
ADT as a reference type:
part2:= ref Inventory;This creates a new
Inventory
and places a reference to it in part2
. The part2
instance can be used like a regular ADT. For example, assigning values to the member data is exactly the same:
part2.id = "Whatsit"; part2.onhand = 1000; part2.cost = 0.17;It is important to note that
part2
has a different type than part
. This means that part2
cannot be assigned to an Inventory
variable or passed directly to member functions of the current Inventory
ADT. For example, the following statements would generate compile-time errors:
part2 = part1; # type clash error, won't compile part2.value(); # argument type mismatch, won't compileTo access a value from a ref ADT, use the asterisk (*) prefix operator:
part1 = *part2; # correct (*part2).value(); # correctADT member functions can accept ref ADT types. This requires the addition of the
ref
keyword to the member function declaration:
clear: fn(item: self ref Inventory);The member function definition looks like this:
Inventory.clear(item: self ref Inventory) { item.onhand = 0; }
However, the interface to a ref ADT can become more obscure, primarily with the issue of being able to overwrite values. The interface to a value ADT is more straightfoward, making it more natural for most circumstances.
union
in C. It enables separate instances of an ADT to hold data elements of different types. In this way, a single ADT declaration can instantiate ADTs of different types. Pick ADTs can have common data and function members and data members that are specific to the ADT instance.
There are two parts to a pick ADT: the pick ADT declaration, and the pick
statement where you access the elements in the pick ADT.
Theadtidentifier
: adt { pick {element
=>variable
:type
; ... } };
element
is the identifier to the pick element, which specifies a particular variable
to be of some type
. For example:
Apick: adt { str: string; pick { String => val: string; # string element Int => val: int; # int element Real => val: real; # real element } };The common member of this ADT is
str
of type string
. The pick elements are String
, Int
, and Real
. These specify three variations of the Apick
ADT.
If a pick ADT has common data members, the pick block must be the final data segement. Member functions can follow the pick block, however.
A pick ADT must be instantiated as a ref ADT. The specific element of the pick must be specified. For example:
r := ref Apick.Real;When you assign values to the data members of the ADT, use the variables declared via the pick structure:
r.str = "Pi"; r.val = 3.141593;You can also declare and initialize an instance of the ADT. For example:
s := ref Apick.String("Greeting", "Hello, World!");
tagof
operator returns the index of the pick ADT element in a particular instance of the ADT. The elements are indexed starting at 0. For example:
tagof rThis returns the index of the
r
instance of the Apick
ADT created above, which is 2
(the third element).
pick
statement enables statement selection based on the variant type of the pick ADT. The general form of the pick
statement is:
pickThe pick statement uses a local copy,localinst
:=refadtinst
{element
=>statements
... }
localinst
, of a reference to the ADT, refadtinst
. The element
(s) can be one or more of the elements
declared in the pick ADT.
t : ref Apick = s; pick u := t { String => sys->print("%s is %s\n", u.str, u.val); Int => sys->print("%s is %d\n", u.str, u.val); Real => sys->print("%s is %f\n", u.str, u.val); }First, prior to the pick statement, a ref ADT of the
s
instance is initialized. Then, the pick
statement selects the statement to execute based on the variant type of the ADT t
.
Another way typically used to accomplish this is to place the pick
statement in a function that accepts as an argument the ref ADT. For example:
printval(a: ref Apick) { pick t := a { String => sys->print("%d: %s\n", tagof t, t.val); Int => sys->print("%d: %d\n", tagof t, t.val); Real => sys->print("%d: %f\n", tagof t, t.val); } }To call this function, the instance of the ADT that specifies the element of the pick is given as the argument:
printval(s);The function can also be a member of the ADT. In this way, the ADT can be passed as an implicit argument. For example:
Apick.printval(this: self ref Apick) { pick t := this { String => sys->print("%d: %s\n", tagof t, t.val); Int => sys->print("%d: %d\n", tagof t, t.val); Real => sys->print("%d: %f\n", tagof t, t.val); } }Then, the call to this function would look like this:
s.printval();Program Listing 2-2 is an simple program that uses a pick ADT.
implement Command;
include "sys.m";
include "draw.m";
sys: Sys;
Apick: adt {
pick {
String => val: string;
Int => val: int;
Real => val: real;
}
printval: fn(this: self ref Apick);
};
Apick.printval(this: self ref Apick) {
pick t := this {
String => sys->print ("%s: %s\n", tag(t), t.val);
Int => sys->print ("%s: %d\n", tag(t), t.val);
Real => sys->print ("%s: %f\n", tag(t), t.val);
}
}
tag(t: ref Apick): string {
r: string;
case tagof t {
tagof Apick.String => r = "String";
tagof Apick.Int => r = "Int";
tagof Apick.Real => r = "Real";
}
return r;
}
Command: module {
init: fn(ctxt: ref Draw->Context, argv: list of string);
};
init(ctxt: ref Draw->Context, argv: list of string) {
sys = load Sys Sys->PATH;
r := ref Apick.Real;
r.val = 3.141593;
sys->print("%d\n", tagof r);
s := ref Apick.String("Hello, World!");
s.printval();
r.printval();
}
This program produces the following output:
2 String: Hello, World! Real: 3.141593