From: seurer@rchland.vnet.ibm.com (Bill Seurer)
Subject: Re: multiple implementations
Date: Mon, 12 Oct 1992 20:07:03 GMT
Reply-To: BillSeurer@vnet.ibm.com
Disclaimer: This posting represents the poster's views, not necessarily those of IBM
Organization: IBM Rochester

In article <718850232snx@black.demon.co.uk>, tchannon@black.demon.co.uk (Tim Channon) writes:
|> > Does anyone know of any good techniques for managing multiple implementation
|> > modules?  A trivial example is for a Stack to have an array implementation
|> > and a linked list implementation.   How do you name your files and how do
|> 
|> Pragmas?
|> 
|>   TC. 
|>     E-mail: tchannon@black.demon.co.uk or tchannon@cix.compulink.co.uk
|>                                 

Nah, you can do it with straight Modula-2 and a bit of indirection.  I
wrote an IBM technical report on this but I don't know if they are
available externally.

This technique was used in something called Cedar Objects that was
mentioned in a class I took one time.  This very topic (multiple
implementations using a "single" definition) was the advanced assignment
for the class.  Almost everyone (including me) had great difficulty
getting it right in the class, but it turns out not to be so tough after
all.

Basically it goes like this...  (Excuse any Modula-2 typos)

1) Create a definition module for an abstraction using an opaque type.  
However, leave out the "create" function.

Example:  Simple (compressed to save space) CARDINAL stack.

DEFINITION MODULE Stack;
TYPE
  T (*Opaque*);
PROCEDURE Push (stack: T;
                item: CARDINAL);
PROCEDURE Pop (stack: T): CARDINAL;
END Stack;


2) Implement the abstraction as a pointer to a record which contains
the data's address and a bunch of procedure variables, one for each
procedure in the definition.

IMPLEMENTATION MODULE Stack;
IMPORT PrivateStack;
TYPE
  T = PrivateStack.T;
PROCEDURE Push (stack: T;
                item: CARDINAL);
BEGIN
  stack^.Push (stack, item);
END Push;
PROCEDURE Pop (stack: T): CARDINAL;
BEGIN
  RETURN stack^.Pop (stack);
END Pop;
END Stack.

DEFINITION MODULE PrivateStack;
TYPE
  T = POINTER TO StackRecord;
  PushProc = PROCEDURE (T, CARDINAL);
  PopProc = PROCEDURE (T): CARDINAL;
  StackRecord = RECORD
    data: SYSTEM.ADDRESS;
    pop: PopProc;
    push: PushProc;
  END;
END PrivateStack;

No PrivateStack implementation is needed.


3) Now create the separate implementations.

I will show just one.

DEFINITION MODULE ArrayStack;
IMPORT Stack;
PROCEDURE Create(): Stack.T;
END ArrayStack.

IMPLEMENTATION MODULE ArrayStack;
IMPORT Stack, PrivateStack;
TYPE
  RealData = POINTER TO RealDataRecord;
  RealDataRecord = RECORD
    topIndex: CARDINAL;
    data: ARRAY[1..99] OF CARDINAL;
  END;
PROCEDURE Create(): Stack.T;
VAR
  new: PrivateStack.T;
  data: RealData;
BEGIN
  NEW (new);
  new^.pop := Pop;
  new^.push := Push;
  NEW (data);
  new^.data := data;
  new^.data^.top := 0;
  RETURN Stack.T(new);
END Create;
PROCEDURE Pop...as you'd expect;
PROCEDURE Push...as you'd expect;
END ArrayStack.


4) Use your Nifty New Type!  You call the specific Create procedure and
the general procedures for everything else.

I bet I made a mistake up there somewhere, but it does work (I have used it)
and there are various things one can do to improve it (like exposing the
opaque type to reduce the calls).  Of course this is
a poor example but it was quite short.

One thing for sure that isn't quite right are the castings to and from
the opaque type.  I have found how this must be done is quite compiler
specific.
-- 

- Bill Seurer     Language and Compiler Development      IBM Rochester, MN
  Internet: BillSeurer@vnet.ibm.com    America On-Line: BillSeurer@aol.com