Reference ADTs

ADTs and tuples are values. Our Point ADT from the previous section behaves like a regular value type. Assigning one point to another makes a copy of the data:

	p := Point (1, 2);
	q := p;
	q.x++;
	sys->print("points are now %s\n",
		(array[] of {"unequal", "equal"})[p.equal(q)]);

will show that the variables do not share a value.

Although this copying is often desirable, it has two potential disadvantages. First, it can be expensive for large structures; and second, there is no way to modify such a data type in a function since the value passed to the function is always a copy. In other words, sometimes a reference type is more suitable.

To create a reference to an ADT, use the keyword ref:

	rp: ref Point;

creates a new variable of type ref Point&emdash;reference to Point&emdash;and initializes it to nil. This initial value means rp has no storage associated with it. Unlike a value ADT, which always has storage associated with the variable, a reference ADT must explicitly be given storage, much like an array variable. Here are a couple of examples:

	rp = ref Point (1, 2);
	rp = ref p;

The first builds a new Point and places a reference to it in rp. The second makes a copy of p and stores a reference to it in rp. It does not create a reference to the same copy of the data as p itself; it makes a reference to a copy. [XXX maybe rq = rp; as contrast to second?]

Once rp is initialized, it can be used like a regular Point: its x component is called rp.x and its y component is called rp.y, for example. But rp has a different type from p; in particular, rp cannot be assigned to a Point variable or passed directly to the member functions of our current Point ADT. Statements such as

	p = rp;         # illegal
	rp.equal(p);    # illegal

will not compile. To extract a Point value from a Point reference, use an asterisk as a prefix operator:

	p = *rp;        # legal
	(*rp).equal(p); # legal

We can of course build member functions of ADTs that accept references. For example, we could define a method to reflect a ref Point about the line x=y by swapping the x and y coordinates:

	Point.reflect(rp: self ref Point)
	{
		(rp.y, rp.x) = (rp.x, rp.y);
	}

(Note the use of a tuple to effect the exchange.) After adding this line to the definition of the Point adt,

		reflect: fn(rp: self ref Point);

we could call it whenever we wanted to execute the operation on a ref Point:

	rp.reflect();

Point.reflect does not allocate storage for the variable&emdash;that must be done before it is called&emdash;but it can overwrite its argument's contents, which cannot be done by a function receiving a value ADT.

Other than being careful about sharing, you can treat reference ADTs very much like value ADTs. The decision of whether to build an interface to an ADT based on values or references is influenced mostly by issues of efficiency: reference ADTs can be passed among functions without copying. But they have disadvantages: the interface can lose clarity because of concerns of overwriting shared storage, while value types display more natural semantics for most uses. Both types of interface are common in Limbo programs.

Exercise: What is wrong with this function to initialize a ref Point variable?

	Point.clear(rp: self ref Point)
	{
		rp = ref origin;
	}

Exercise: Discuss the advantages and disadvantages of changing Point.add to accept a ref Point instead of a plain Point.

previous next

© Rob Pike and Howard Trickey 1997. All rights reserved.