6.170 / Spring 2004 / Writing Abstraction Functions & Rep Invariants

Handout S8

Contents:


Introduction

This document gives some hints for writing abstraction functions and rep invariants.  Chapter 5 of the Liskov book describes these concepts in some detail.  This document focuses on practical issues, such as how to make your abstraction functions and rep invariants easy to read and understand, and common idioms for writing them.

Abstraction Functions

An abstraction function AF maps the concrete representation of an abstract data type to the abstract value space that the ADT represents.  Formally,
AF: R => A
where R is the set of rep values, and A is the set of abstract values.  You can think of an element in R as the Java object.  A, on the other hand, exists only in our imagination, and has no existence inside the computer.  For example, if this is the ADT:
public class Complex {
private double real;
private double imag;
...
}
then the rep space R is the set of Complex objects, and the abstract space A is the set of complex numbers.

Where Are R and A Defined?

The rep space R is obvious: it's defined by the fields you put in your class.  You can't possibly implement an abstract data type without fields, so you'll never forget this.  The abstract value space A, however, is not represented in code.  It should be documented in your class overview, because both clients and implementors want to know what abstract type the class represents:
/**
* Complex represents an immutable complex number. <=== this is A
*/
public class Complex {
private double real; <=== these fields form R
private double imag;
...
}
Whether the type is mutable or immutable is a crucial property that should be mentioned in the class overview.

AF(r)

Technically, an abstraction function is a function.  This is why you may see it written using functional notation, AF(r):
/**
* Complex represents an immutable complex number.
*/
public class Complex {
private double real;
private double imag;

// The abstraction function is
// AF(r) = r.real + i * r.imag
...
}
The r in AF(r) represents an element in the rep space.  In other words, it's a reference to a Complex object.  So we can refer to fields of the object r on the right-hand side of the abstraction function.  (Incidentally, r stands for representation; the Liskov book names this variable c, for concrete, but it means the same thing.)

The functional notation is essential when the abstraction function is recursive, since we need some way to refer to it on the right-hand side:
/**
* Cons represents an immutable sequence of objects.
*/
public class Cons {
private Object car;
private Cons cdr;

// The abstraction function is
// AF(r) = [] if r = null
// [r.car] : AF(r.cdr) if r != null
...
}
(The square brackets and colons are sequence construction syntax.  See the next section for more details.)

However, usually the abstraction function isn't recursively defined.  Then it's more readable just to write the right-hand side.  We further assume that the rep object r represents this, and adopt the convention of dropping references to this when writting the abstraction function:
/**
* Complex represents an immutable complex number.
*/
public class Complex {
private double real;
private double imag;

// The abstraction function is
// real + i * imag
...
}
This is simple and clear, but remember that it's just shorthand for AF(r).

Mathematical Abstract Values

For some data types, the abstract value space consists of familiar mathematical objects:
If so, you're in luck.  You can use conventional notation for constructing and manipulating abstract values on the right-hand side of your abstraction function:
You aren't obliged to use this syntax.  Some of it is more standard than the rest: everybody with some training in mathematics recognizes set comprehension syntax, but sequence concatenation isn't particularly standardized.  You may find it clearer to write sequence concatenation as a function like concat(x, y).  What really matters is clarity and lack of ambiguity, so if you have any doubt whether your reader will understand you, just define it: "...where concat(x,y) is the concatenation of two sequences x and y."

Specification Fields

More frequently, the abstract values are not simple mathematical objects.  In these cases, it's more useful to think about the abstract value as if it were an object with fields.  For example, a line has a start and an end; a mailing address has a number, street, city, and zipcode; and a URL has a protocol, host name, and resource name.

Mathematically, this is the same as a tuple; it's just a tuple whose parts are named, rather than simply numbered.  So we could specify the abstraction function as a single function that maps representation objects into tuples.  But it's convenient, and more readable, to break the abstraction function into several separate functions, each viewed as defining a specification field.  (Specification fields are more commonly called abstract fields, because they're fields of the abstract value, as opposed to rep fields which are fields of the rep value.  Unfortunately, abstract has another meaning in Java, so we will avoid that potentially confusing terminology.)

Specification fields often (but not always) correspond to observers on the abstract data type.  Because the structure of abstract values is a matter of interest to the clients of your class, the specification fields should be listed in the class overview.  In 6.170, we have a Javadoc convention for describing them: @specfield name : type // description.  Here's an example:
/**
* Line represents an immutable line segment in the plane.
* @specfield start : Point // start point
* @specfield end : Point // end point
*/

By convention, in specification fields, lowercase types like sequence or set refer to mathematical entities. Capitalized types refer to other ADTs (i.e., Java classes).  Where you have a choice, prefer a mathematical entity as the type of a spec field; better to use sequence than List, for example.  It's more elegant, and reduces the coupling between your specification and concrete Java classes.

For ADTs with trivial reps, the spec fields may correspond one-to-one with rep fields:
public class Line {
private Point start;
private Point end;

// Abstraction function is
// AF(r) = line l such that
// l.start = r.start
// l.end = r.end
...
}
But this abstraction function is hardly worth writing down.  Here's a more interesting rep for the same ADT:
public class Line {
private Point start;
private double length;
private double angle;

// Abstraction function is
// AF(r) = line l such that
// l.start = r.start
// l.end.x = r.start.x + r.length * cos(r.angle)
// l.end.y = r.start.y + r.length * sin(r.angle)
...
}
Note that x and y are spec fields of the Point type.  It was convenient to define the point end in terms of its spec fields as well.

Let's simplify this with some more shorthand.  We'll drop the AF(r), as we did earlier.  We'll assume that the rep value r and the abstract value t both represent this --- just different aspects of this -- and adopt the convention of dropping references to this.   The effect of all this shorthand is just a list of equations defining each spec field in terms of the concrete fields:
    // Abstraction function is
 // start = start
// end.x = start.x + length * cos(angle)
// end.y = start.y + length * sin(angle))
Keep in mind that on the left-hand side, start refers to a spec field; on the right-hand side, start refers to a concrete field.  (x and y always refer to spec fields here, because the rep of the Point type isn't visible to us.)  Isn't there a danger of confusion between the two starts?  Not really. We gave the spec field and the concrete field the same name for good reason -- because they are equated by the abstraction function. Do we really have to say start=start explicitly in the abstraction function?  Probably not. If the spec field start and the rep field start weren't equated by the abstraction function, then we should have given them different names.  Remember that your goal in these kinds of specifications is not formal communication with a machine, but clear and unambiguous communication with another human being.  Names matter.

Here's another example.  Suppose we want to represent a Card data type, in a poker game, using a single integer in a field index.  We might have two specification fields, suit and value:
/**
* Card represents an immutable playing card.
* @specfield suit : {Clubs,Diamonds,Hearts,Spades} // card suit
* @specfield value : {Ace,2,...,10,Jack,Queen,King} // card rank
*/
The abstraction function then describes how to peel apart the index field into a suit and a value:
public class Card {
private int index;

// Abstraction function is
// suit = S(index div 13) where S(0)=Clubs, S(1)=Diamonds, S(2)=Hearts, S(3)=Spades
// value = V(index mod 13) where V(1)=Ace, V(2)=2, ..., V(10)=10,
// V(11)=Jack, V(12)=Queen, V(0)=King
// (div and mod refer to the integer division and remainder operations)
// for example, 3 => Three of Clubs
// 14 => Ace of Diamonds
 ...
}
This abstraction function maps each representation object to a pair (suit,value), but rather than writing it as a single function, we've specified it as two separate ones, one for each specification.

(Two incidental points: you may wonder why Ace is V(1) instead of V(0).  This was done to make the abstraction function more direct on the numbered cards, so that V(i) = i for 2 through 10.  But it may not be ideal; since King is V(0), we can't compare cards by comparing the index field directly.  Second, this representation of Card is tightly coupled to the set of suits and the set of values.  If we expect those types to change in the future, e.g., adding or removing suits, then we would have to change the representation of Card.  For some applications, however, the compactness of the representation may be worth the tradeoff in greater coupling.)

Using Spec Fields for Specifications

In addition to their use in abstraction functions, spec fields are also useful for writing specifications of the ADT's operations.  Here's a specification for a method on the Line class:

/**
* @requires start not equal to end
* @return angle in radians between this line segment and a horizontal ray starting at start
* and extending to the right. The angle is measured in a counter-clockwise direction.
 */
public double getAngle ();

Specifications may refer to specification fields, but never to representation fields. Rep fields depend on a particular implementation, so we don't want to expose them in a specification, which can be implemented in many different ways.

When you're writing specifications for operations, you may find it useful to define derived spec fields.  A derived spec field is just a spec field that can be written in terms of other spec fields.  In other words, it's a shorthand.  We use the syntax @derivedfield name : type // description = definition to define a derived field.  Like spec fields, this syntax appears in the class overview:

/**
* Line represents an immutable line segment in the plane.
* @specfield start : Point // start point
* @specfield end : Point // end point
* @derivedfield length : real // length of segment = dist(start, end)
*/
Because length is defined only in terms of  the spec fields start and end, you don't have to define it in your abstraction function.  In fact, a derived spec field may not be defined in terms of concrete fields.  If you find you need to mention a concrete field in order to define a derived spec field, then it shouldn't be a derived spec field at all, and its definition shouldn't appear in the class overview.  It should be an ordinary spec field instead, and its definition should appear in the abstraction function, visible only to the implementor or maintainer of the class.  So it would be wrong to try to make end into a derived spec field, e.g.:
/**
* Line represents an immutable line segment in the plane.
* @specfield start : Point // start point
* // THIS IS WRONG: refers to rep fields length and angle
* @derivedfield end : Point // end point =
* end.x = start.x + length * cos(angle)
* end.y = start.x + length * cos(angle)
*/
If we added length and angle as spec fields, then this would be OK --- but the more rep fields you duplicate as spec fields, the less abstraction (detail-hiding) your class actually provides.  Judge carefully whether a property is important to clients of the type before adding it as a spec field or derived spec field.  Slavishly repeating all the rep fields as spec fields is almost always pointless, unless the class is a trivial ADT (which Liskov calls a record type, section 5.3.4).

You can freely use derived spec fields in a method specification:
/**
* @requires length > 0
* @return angle in radians between this line segment and a horizontal ray starting at start
* and extending to the right. The angle is measured in a counter-clockwise direction.
*/
public double getAngle ();

Simplifying specifications is in fact the only real purpose of derived spec fields.

The presence of a specification field does not imply anything about the interface or implementation of the class. Although spec fields often correspond to observer methods, that's not always true.  The interface might not provide any observers that query the spec field's state, so clients of the class might not have access to the information stored in the spec field. Likewise, the implementation might not actually have a concrete field of the spec field's type: that information may be computed from multiple concrete fields, or it might not be available at all. The point is that specification fields are a convenience for use in abstraction functions and specifications.

Rep Invariants

A rep invariant RI maps the concrete representation to a boolean (true or false).  Formally,
RI: R => boolean
where R is the set of rep values.  The rep invariant describes whether a rep value is a well-formed instance of the type.

The comment describing a rep invariant may explicitly emphasize the functional aspect of RI:
 // Rep invariant is
// RI(r) = r.name != null && r.balance >= 0
More commonly, however, we just drop the RI(r) detail and simply write the rep invariant as a predicate that must be true of this:
 // Rep invariant is
// name != null && balance >= 0
A rep invariant may mix concrete Java syntax (rep field references, method calls, instanceof, !=, ==) with abstract mathematical syntax (sequence/set/tuple construction, for all, there exists, summation, =).   A rep invariant may also be simple English, of course, as long as it is unambiguous.  Here are some examples:
 //    for all i, transactions[i] instanceof Trans

// suit in {Clubs,Diamonds,Hearts,Spades}

// string contains no duplicate characters

// size = number of non-null entries in array
If the rep uses other ADTs, it may refer to them either by their spec fields or by their operations.  For example, suppose the Trans type has a spec field amount that is accessible by the operation getAmount.  Then the rep invariant for Account (which uses Trans objects) might look like this:
(1)   //    balance == sum (for all i) of transactions[i].amount
or this:
(2)   //    balance == sum (for all i) of transactions[i].getAmount()
or even this (since transactions is an instance of an ADT with a get operation):
(3)   //    balance == sum (for all i) of transactions.get(i).getAmount()
All these are equivalent.  But it's important to keep in mind that amount in (1) above refers to a spec field in Trans, not to a concrete field.  Unless Account and Trans are cooperating very closely, it isn't appropriate for Account to break the abstraction barrier and refer to the rep fields of  Trans directly.  By contrast, it's perfectly normal for Account's rep invariant to refer directly to Account's rep fields.

Choosing the Rep Invariant

Writing down a rep invariant is tremendously useful for testing, debugging, and writing correct code.  It's essential for maintainers, programmers who come back to fix or enhance the code later.  The most common problem with rep invariants is incompleteness -- leaving out something important.  (Leaving out the rep invariant entirely is probably the most common example of this problem!)  So here are some hints that will help you fill out your rep invariants.

Look for rep values on which the abstraction function has no meaning.  The abstraction function is often a partial function, meaning that it isn't defined for some values in R.  The rep invariant must exclude any such values.  Recall the Card example from above:
public class Card {
private int index;

// Abstraction function is
// suit = S(index div 13) where S(0)=Clubs, S(1)=Diamonds, S(2)=Hearts, S(3)=Spades
// value = V(index mod 13) where V(1)=Ace, V(2)=2, ..., V(10)=10, V(11)=Jack, V(12)=Queen,
// V(0)=King
 ...
}
In this case, the abstraction function isn't defined when the suit number, index div 13, is anything but 0, 1, 2, or 3.  So that rules out values of index less than 0 or greater than 51:
    // Rep invariant is
// 0 <= index <= 51
Make sure you don't restrict the rep invariant so much that the abstraction function no longer covers the entire abstract value space A!  If you do that, you'll no longer be implementing the abstract type, since some abstract values will be unrepresentable.  Here, a little thought convinces us that index still represents 52 cards, all distinct.

Look for rep values on which your methods would produce the wrong abstract value. 
Consider a class CharSet that represents a set of characters using a mutable string:
/** CharSet represents a mutable set of characters. */
public class CharSet {
private StringBuffer chars;

// Abstraction function is
// { chars[i] | 0 <= i < chars.size }
 ...

/** @modifies this
* @effects this_post = this - {c}
*/
public void remove (char c) {
int i = chars.indexOf(Character.toString (c));
if (i != -1) chars.deleteCharAt (i);
}
}
This implementation of the remove method only works if there are no duplicates in the string, because it only deletes the first occurrence of c that it finds.  Notice that the abstraction function is fully defined on R here -- it doesn't care whether or not there are duplicate characters, because the set construction syntax implicitly ignores them.  But we need the rep invariant to ensure that remove always results in the correct abstract value, i.e. a set that does not contain c.

Look for constraints required by the data structures or algorithms you've chosen.  For example, an array must be sorted in order to use binary search.  A tree cannot have cycles, and two nodes cannot share the same child.

Look for fields that need to stay synchronized with each other.  In a bank account, for example, the balance and the sum of the transaction amounts should always be in synch.  In a linked list, the size field always needs to accurately reflect the number of nodes in the list.

Look for rep values that would cause your code to throw unexpected exceptions. A conventional part of every rep invariant is a set of fields that shouldn't be null, so you won't have any NullPointerExceptions when you use them later:
    //   name != null && transactions != null
Make sure your constructors and producers actually establish the rep invariant, though.  That means, if any of these fields are initialized from parameters, you have to check for null before you put them in the rep.

Some other exceptional conditions you should think about:
The last one is particularly important if you're using a generic data type, like a collection, to store specific types of objects.  Your rep invariant should specify the types of the objects in the collection:
    //   for all i, transactions[i] instanceof Trans

// transactions is a list of Trans objects (less mathematical, probably clearer)

// studentsToSections maps Student to Integer
Java 1.5 (coming soon) will improve this situation by allowing you to declare the type of the contained objects as a parameter to the collection's type:
    List<Trans> transactions; // this syntax works only in Java 1.5
Map<Student,Integer> studentsToSections; // only in Java 1.5
Once that's the case, you'll be able to leave this out of your rep invariant, because the Java runtime system will check it for you automatically.  Until then, however, put it in your rep invariant; better safe than sorry.

Incidentally, many Java programmers are preparing for Java 1.5 by putting the types in the declaration as a comment:
    List/*<Trans>*/ transactions;
Map/*<Student,Integer>*/ studentsToSections;
That's helpful documentation, and forward thinking, but the compiler isn't paying any attention to it.  So you still need to check the types in your rep invariant -- particularly checkRep, the exectable form of your rep invariant described in the next section.

Look for constraints imposed by the application domain (on abstract values).  In a bank account, the balance should always be nonnegative.  (Or even stronger: the partial sums of the transaction amounts must all be nonnegative, to guarantee that the balance never went negative in the past.)  In chess, one white bishop should always be on a white square, while the other white bishop should be on a black square.  In a recitation section assignment, every student must be assigned to exactly one section, and no section may have more than 20 students (for example).

Assuming they refer only to properties of the abstract values (like spec fields), these kinds of constraints do not just apply to the rep.  They are in fact abstract invariants (Liskov section 5.7.3).  Abstract invariants should be documented for the client of the data type, so put them in your class overview.  But they're also part of the representation invariant.  If you omitted them from the rep invariant, then your type would represent abstract values that aren't well-formed.

Checking the Rep Invariant at Runtime

Many rep invariants can be translated straightforwardly into code.  If that's the case, do it!  Having an executable rep invariant, and using it at runtime, not only helps find bugs in the code quickly; it also checks for mistakes in the rep invariant.  Sometimes the rep invariant you've written is too strong, making assumptions that are unwarranted and unnecessary.  Actually testing the invariant against running code is a good sanity check.

The rep invariant checker can be coded as a method checkRep of no arguments that throws an exception if the rep invariant is violated, preferably with a message indicating which constraint was broken.  Here's an example:
/** @effects: nothing if this satisfies rep invariant;
              otherwise throws an exception
*/
private void checkRep () {
    if (balance < 0) 
        throw new RuntimeException ("balance should be >= 0");

    if (name == null)
        throw new RuntimeException ("name should be non-null");

    // for all j, sum (i=0..j) transactions[i].amount >= 0
    int b = 0;
    for (int i = 0; i < transactions.size(); i++) {
        Trans t = (Trans) transactions.get(i);
        b += t.getAmount();
        if (b < 0)
           throw new RuntimeException("balance went negative");   
    }
    // balance = sum (for all i) transactions[i].amount
    if (balance != b)
        throw new RuntimeException ("balance should equal sum of transactions[i].amount");
}
The best place to put checkRep in your class is right after your fields.  You can either write your rep invariant as a separate comment, or intersperse the constraints of your rep invariant as exception messages in checkRep (as was done above).  The latter approach is probably better, because it makes it more likely that the comment will be kept current with the code in checkRep. Any part of the rep invariant that you can't write as executable code, just leave as a comment.

Assertions provide an even cleaner way to write checkRep, because they handle the test and exception for you.  Here are two ways to
do it:
    // using junit.framework.Assert (you don't have to be writing a unit test to use this class)
Assert.assertTrue ("balance should be >= 0", balance >= 0);

// using Java 1.4 assert syntax
 assert balance >= 0 : "balance should be >= 0";
Calls to checkRep should be placed at the start and end of every public mutator, at the end of every constructor.  For an observer, the call to checkRep should be placed at the start, and at the end also if the observer changes the rep -- and even if it supposedly doesn't (since you may be wrong).  Private methods generally don't call checkRep, because private methods may be called while the rep is in an intermediate state that doesn't satisfy the rep invariant.

If part of the rep invariant is very expensive to check, you may want to turn that part off in the release version.  Otherwise it makes sense to leave it in.  Be careful how you judge performance here.  Novices are often much too ready to worry about performance improvements that turn out to be negligible.  Before dropping a check, you should have some evidence that it's expensive, such as an analysis with a profiler showing that indeed the check is a hotspot, or a theoretical argument, for example that the check turns a constant time operation into a linear time one.  Checking fields against null is always a constant time operation; there's no reason to drop this check except in absolutely performance-critical code.  Summing all the transactions in an account is linear in the number of transactions, which you might not want to do for every method call in production code.  But even expensive checkReps are justified during testing and debugging; they'll pay for themselves in reduced debugging time.

The checkRep method can also be made public, so that unit tests can call it during testing. checkRep is normally private, since the representation is not part of the specification; clients aren't supposed to be aware that there's a rep under the covers that might be broken. (Horrors!) But it doesn't expose the rep if you make it public. If your class is working properly, then from the client's point of view, checkRep is just a no-op. If you make checkRep public, you should probably specify it that way, so that clients don't bother calling it unless they're paranoid: "This operation does nothing unless there's a bug, in which case it (sometimes) throws an exception." Needless to say, don't put the rep invariant itself in the spec for checkRep. That's representation-dependent.

A Complete Example

Here's a complete example of a simple class with an abstraction function and a rep invariant, so you can see one way you might structure your code.
/**
 * Card represents an immutable playing card.
 * @specfield suit : {Clubs,Diamonds,Hearts,Spades} // card suit
 * @specfield value : {Ace,2,...,10,Jack,Queen,King} // card rank
 */
public class Card {

    private int index;

    // Abstraction function is
    //    suit = S(index div 13)   where S(0)=Clubs, S(1)=Diamonds, S(2)=Hearts, S(3)=Spades
    //    value = V(index mod 13)  where V(1)=Ace, V(2)=2, ..., V(10)=10, V(11)=Jack, V(12)=Queen,
    //                                   V(0)=King

    // Rep invariant is
    //    0 <= index <= 51

    /** 
     * Check the rep invariant.
     * @effects: nothing if this satisfies rep invariant;
     *           otherwise throws an exception
     */
    private void checkRep() {
        if (index < 0 || index > 51)
            throw new RuntimeException ("card index out of range");
    }

    /**
     * @effects makes a new playing card with given suit and value
     */
    public Card(CardSuit suit, CardValue value) {
        ... // initialize Card
        checkRep ();
    }

    /**
     * @effects returns this.suit   <=== "suit" refers to the spec field
     */
    public CardSuit getSuit() {
        checkRep ();

        CardSuit s = ... // decode suit

        checkRep ();
        return s;
    }

    /**
     * @effects returns this.value  <=== "value" is the spec field
     */
    public CardValue getValue() {
       checkRep ();

       CardValue v = ... // decode value

       checkRep ();
       return v;
    }

    ...
}
The calls to checkRep() in all the observers are probably unnecessary in this case, since the class is simple, immutable, and clearly has no rep exposure.  They're included anyway to illustrate what you would want to do in a more complex class.

General Hints on Readability

Remember that the reader of your abstraction functions and rep invariants is most likely going to be a human being, not a program.   It might be a teammate trying to find a bug in the code; a maintainer charged with updating the software after you've left the company; or even yourself, six months later, trying to remember how this program worked.

So formal syntactic correctness is actually less important than simplicity and clarity.  That doesn't mean you should sacrifice semantic precision, or leave things ambiguous or undefined.  But it does mean that you should think twice about writing something like this:
    // Abstraction function is
// <[x,y],s^[for all 0<i<=chars.size(), chars[chars.size()-i]]> | x=front.cdr,
// y=back.car, s=n.toString()>
A compiler would jump for joy at reading this (if the compiler were actually reading your abstraction function).  A human would stare at it and curse.

Here are some tips for making your abstraction functions and rep invariants more readable:


Back to the 6.170 home page.
For problems or questions regarding this page, contact: 6.170-webmaster@mit.edu.