16.7 Extended ADT Examples

This section contains more complete examples of abstract data types realized in Generic Modula-2. The design work for these modules was done in VDM-SL (Vienna Documentation Method-Specification Language) by Matt Suderman for an undergraduate thesis submitted in partial fulfillment of a B.Sc. degree at Trinity Western University in the Spring of 1997. Only a general discussion and the end result in Generic Modula-2 is give here.

16.7.1 Generic Lists

For this example, the ADT List has been chosen. The generic aspects are the type of the data and a procedure to do assignments. In order to make a close fit with the design notation, a type has been introduced to model the Natural Numbers, that is, the positive whole numbers (not including zero). This is used to number the list. In addition, take note of the systematic use of preconditions and the provision of procedures to check those preconditions. As usual, various procedures are expressed in terms of the formal parameter names whose values are yet to be supplied.

GENERIC DEFINITION MODULE Lists (Data: TYPE; AssignData: AssignProcType);

TYPE
  AssignProcType = PROCEDURE (VAR Data; Data);
  (* ``Nat'' means ``Natural Number'' as in VDM-SL *)
  Nat1 = [1 .. MAX (CARDINAL)];
  Nat = CARDINAL;
  Traversal = PROCEDURE (Data): Data;
  List;

PROCEDURE InitList (VAR list: List);
(* Post: Empty (list) = TRUE *)
   
PROCEDURE CanInsert (list: List; position: Nat1): BOOLEAN; 
(* Pre:  'memory available' AND position <= Length (list) + 1 *)
PROCEDURE Insert (VAR list: List; data: Data; position: Nat1);
(* Post: GetLength (list') + 1 = GetLength (list) 
      AND GetElement (list', 1..position - 1) 
         = GetElement (list, 1..position - 1)
      AND GetElement (list', position..GetLength (list')) 
         = GetElement (list, position + 1..GetLength (list))
      AND GetElement (list, position) = data *)

PROCEDURE CanUpdate (list: List; position: Nat1): BOOLEAN;
(* Pre: position <= Length (list) *)
PROCEDURE Update (VAR list: List; data: Data; position: Nat1);
(* Post: GetLength (list') = GetLength (list) 
      AND p <> position 
         ==> GetElement (list', p) = GetElement (list, p) 
      AND GetElement (list, position) = data *)

PROCEDURE CanAppend (list: List; data: Data): BOOLEAN;
(* Pre: 'memory available' AND NOT Empty (list) *)
PROCEDURE Append (VAR list: List; data: Data);
(* Post:  GetLength (list') + 1 = GetLength (list) 
      AND GetElement (GetLength (list)) = data 
      AND GetElement (list', 1..GetLength (list')) 
         = GetLength (list, 1..GetLength (list')) *)

PROCEDURE CanDelete (list: List; position: Nat1): BOOLEAN;
(* Pre: position <= GetLength (list) *)
PROCEDURE Delete (VAR list: List; position: Nat1);
(* Post: GetLength (list') = GetLength (list) + 1  
      AND GetElement (list', 1..position - 1) 
         = GetElement (list, 1..position - 1)
      AND  GetElement (list', position + 1..) 
         = GetElement (list, position ..) *)
 
PROCEDURE Traverse (VAR list: List; traversal: Traversal);
(* Post: for all p GetElement (list, p) 
         = traversal (GetElement (list', p)) *)
   
PROCEDURE GetLength (list: List): Nat;
(* Post: the number of elements in the list *)

PROCEDURE Empty (list: List): BOOLEAN;
(* Post: GetLength (list) = 0  Empty (list) = TRUE *)   

PROCEDURE CanGetElement (list: List; position: Nat1): BOOLEAN;
(* Pre: position <= GetLength (list) *)
PROCEDURE GetElement (list: List; position: Nat1): Data;
(* Post: GetElement (list, position) = data at position in the list *)

END Lists.

The implementation part of this generic separate module supplies the code to make everything happen, again expressed in terms of the formal parameters. The formal design of this module included various preconditions, and implementation of these require that one be able to determine ahead of time whether storage space is available. To meet this requirement, a local module is employed that buffers one storage location. Whenever this is used, another one is requested. The function StorageAvailable can examine the buffered location and determine if a valid one is available, and calls to MYNEW return the location available and attempt to replenish the buffer for the next call. Matt has formulated his list design as an inherently recursive structure. A list points to another list (sometimes called its tail) recursively until there are no more data items.

GENERIC IMPLEMENTATION MODULE Lists (Data: TYPE; AssignData: AssignProcType);

FROM Storage IMPORT ALLOCATE, DEALLOCATE;

TYPE
  NodePtr = POINTER TO Node;
  Node = RECORD 
      data: Data;
      next: NodePtr;
    END;
  List = NodePtr;

MODULE MyStorage;
(* Module ``MyStorage'' is necessary to implement the function
 ``StorageAvailable''.  ``StorageAvailable'' returns true if
 ``MYNEW'' will return a new non-NIL pointer to a node; otherwise,
 if ``MYNEW'' will return a NIL pointer ``StorageAvailable'' returns
 false. In other words, ``StorageAvailable'' returns true if another
 ``Node'' can be allocated in the system heap and false otherwise. *)
 
IMPORT ALLOCATE, DEALLOCATE, Node, NodePtr;

EXPORT MYNEW, StorageAvailable;

VAR temp: NodePtr;

PROCEDURE StorageAvailable (): BOOLEAN;
BEGIN
  RETURN temp <> NIL;
END StorageAvailable;

PROCEDURE MYNEW (init: Node): NodePtr;
(* return the buffered value, and if it was not NIL, get a new one *)
VAR
  new: NodePtr;
BEGIN
  IF temp = NIL
    THEN
      RETURN NIL
  ELSE
    new := temp;
    new^ := init;
    NEW (temp);
    RETURN new
  END
END MYNEW; 

BEGIN (* initialize one location in the buffer called temp *)
  NEW (temp);
END MyStorage; (* end f local module *)

(* resume declarations in main module *)

PROCEDURE InitList (VAR list: List);
BEGIN
  list := NIL;
END InitList;

PROCEDURE MkNode (data: Data; next: NodePtr): Node;
VAR
  node: Node;
BEGIN
  AssignData (node.data, data);
  node.next := next;
  RETURN node;
END MkNode;

PROCEDURE IsEmpty (list: List): BOOLEAN;
BEGIN
  RETURN list = NIL
END IsEmpty; 

PROCEDURE Length (list: List): Nat;
(* observe his formulation of length as a recursive procedure. This may take longer to calculate, but he does not have to store and update a value *)
BEGIN
  IF NOT IsEmpty (list) THEN
    RETURN 1 + Length (list^.next);
  ELSE
    RETURN 0;
  END;
END Length;

PROCEDURE PtrToNode (list: List; position: Nat1): NodePtr;
(* work along the list until the correct position is found, then return a pointer to the rest of the list *)
BEGIN
  IF position > 1
    THEN
      RETURN PtrToNode (list^.next, position - 1);
    ELSE
      RETURN list;
    END;
END PtrToNode;

(* The next three procedures implement the typical inserting possibilities. *)

PROCEDURE InsertAtBeginning (VAR list: List; data: Data); 
VAR
  new: NodePtr;
BEGIN
  new := MYNEW (MkNode (data, list));
  list := new;
END InsertAtBeginning;

PROCEDURE InsertAfter (ptr: NodePtr; data: Data);
VAR
  new: NodePtr;
BEGIN
  new := MYNEW (MkNode (data, ptr^.next));
  ptr^.next := new;
END InsertAfter;

(* This one is the exported procedure and must select one of the above. *)

PROCEDURE Insert (VAR list: List; data: Data; position: Nat1);
BEGIN
  IF position = 1
    THEN
      InsertAtBeginning (list, data);
    ELSE
      InsertAfter (PtrToNode (list, position - 1), data);
    END;
END Insert;

PROCEDURE CanInsert (list: List; position: Nat1): BOOLEAN; 
BEGIN
  RETURN (position <= Length (list) + 1) AND StorageAvailable (); 
END CanInsert;

PROCEDURE Append (VAR list: List; data: Data);
VAR
  ptr: NodePtr;
BEGIN
  ptr := list;
  IF ptr = NIL
    THEN
      InsertAtBeginning (list, data);
    ELSE
      WHILE ptr^.next <> NIL
        DO 
          ptr := ptr^.next;
        END;
      InsertAfter (ptr, data);
    END;
END Append;

PROCEDURE CanAppend (): BOOLEAN;
BEGIN
  RETURN StorageAvailable ();
END CanAppend;

PROCEDURE Update (VAR list: List; data: Data; position: Nat1);
VAR
  ptr: NodePtr;
BEGIN
  ptr := PtrToNode (list, position);
  AssignData (ptr^.data, data);
END Update;

PROCEDURE CanUpdate (list: List; position: Nat1): BOOLEAN;
BEGIN
  RETURN (position <= Length (list)) AND StorageAvailable ();
END CanUpdate;        

(* The next three procedures implement the typical deleting possibilities. *)

PROCEDURE DeleteAtBeginning (VAR list: List);
VAR
  temp: NodePtr;
BEGIN
  temp := list;
  list := list^.next;
  DISPOSE (temp);
END DeleteAtBeginning;

PROCEDURE DeleteAfter (ptr: NodePtr);
VAR
  temp: NodePtr;
BEGIN
  temp := ptr^.next;
  ptr^.next := temp^.next;
  DISPOSE (temp);
END DeleteAfter;

(* This one is the exported procedure and must select one of the above. *)

PROCEDURE Delete (VAR list: List; position: Nat1);
BEGIN
  IF position = 1
    THEN
      DeleteAtBeginning (list);
    ELSE
      DeleteAfter (PtrToNode (list, position - 1));
    END;
END Delete;

PROCEDURE CanDelete (list: List; position: Nat1): BOOLEAN;
BEGIN
  RETURN position <= Length (list);
END CanDelete;

PROCEDURE Traverse (VAR list: List; traversal: Traversal);
VAR
  ptr: NodePtr;
BEGIN
  ptr := list;
  WHILE ptr <> NIL
    DO
      AssignData (ptr^.data, traversal (ptr^.data));
      ptr := ptr^.next;
  END;
END Traverse;

PROCEDURE GetLength (list: List): Nat;
BEGIN
  RETURN Length (list);
END GetLength;

PROCEDURE Empty (list: List): BOOLEAN;
BEGIN
  RETURN IsEmpty (list);
END Empty;

PROCEDURE GetElement (list: List; position: Nat1): Data;
VAR
  ptr: NodePtr;
BEGIN
  ptr := PtrToNode (list, position);
  RETURN ptr^.data;
END GetElement;

PROCEDURE CanGetElement (list: List; position: Nat1): BOOLEAN;
BEGIN
  RETURN position <= Length (list);
END CanGetElement;

END List.

16.7.2 Generic Queues

Here, Matt provides a generic definition module for the type Queue. Implementation is fairly simple and can be based directly on the List module in the previous subsection.

GENERIC DEFINITION MODULE Queues (Data: TYPE; AssignData: List.AssignProcType);

TYPE
  Queue;
  Nat = CARDINAL;

PROCEDURE InitQueue (VAR q: Queue);
(* Post: Empty (q) = TRUE *)

PROCEDURE CanEnqueue (q: Queue; d: Data): BOOLEAN;
(* Pre: 'memory available' *)
PROCEDURE Enqueue (VAR q: Queue; d: Data);
(* Post: the Length (q) time, Dequeue (q) is called, Dequeue (q) = d *)

PROCEDURE CanDequeue (q: Queue): BOOLEAN;
(* Pre: NOT Empty (q) *)
PROCEDURE Dequeue (VAR q: Queue): Data;
(* Post: Dequeue (q') = GetHead (q') *)

PROCEDURE CanGetHead (q: Queue);
(* Pre: NOT Empty (q) *)
PROCEDURE GetHead (q: Queue): Data;
(* Post: GetHead (q) = the oldest enqueued element *)
 
PROCEDURE Length (q: Queue): Nat;
(* Post: Length (q) = the number of elements enqueued - number dequeued *)

PROCEDURE Empty (q: Queue): BOOLEAN;
(* Post: Length (q) = 0  Empty (q) = TRUE *)

END Queues.

16.7.3 Generic Stacks

Finally, he gives a generic definition module for the type Stack. Once again, implementation is fairly simple and is left to the student as an exercise.

GENERIC DEFINITION MODULE Stacks (Data: TYPE; AssignData: List.AssignProcType);

TYPE
     Stack;
     Nat = CARDINAL;

PROCEDURE InitStack (VAR s: Stack);
(* Post: Empty (s) = TRUE *)

PROCEDURE CanPush (s: Stack; d: Data): BOOLEAN;
(* Pre: 'memory available' *)
PROCEDURE Push (VAR s: Stack; d: Data);
(* Post: Pop (s) = d *)

PROCEDURE CanPop (s:Stack): BOOLEAN;
(* Pre: NOT Empty (s) *)
PROCEDURE Pop (VAR s:Stack): Data;
(* Post: Pop (s') = GetTop (s') *)

PROCEDURE CanGetTop (s: Stack): BOOLEAN;
(* Pre: NOT Empty (s) *)
PROCEDURE GetTop (s: Stack);
(* Post: GetTop (s) = the list element to be pushed *)

PROCEDURE Length (s: Stack): Nat;
(* Post: Length (s) = the number of elements pushed - number popped *)

PROCEDURE Empty (s: Stack): BOOLEAN;
(* Post: Length (s) = 0  Empty (s) *)

END Stacks.

Contents