The ModulAtor

Ubaye's First Independent Oberon-2 & Modula-2 Technical Publication

Nr. 84, Nov-2001

ModulaTor logo, 7.8KB

A language extension for programming abstractions without objects

by Eugene Shcherbatyuk , 29-Oct-2001

The title may bring up many questions. Some of them are: What language to extend? What 'programming abstractions' is assumed to be? Whether 'programming abstractions' will pay language extension effort? Why not to use objects for the purpose?

As you could notice the questions are directed to reveal the background and motivation. The fine details of the proposed language extension are of relatively minor importance. There would be found many ways to accomplish the task if the task would be considered promising one. The questions will be answered and then the idea of the proposed language extension will be outlined. The point is that objects have some attractive possibilities, but seem to be somewhat alien in the environment of modular languages such as Modula and Oberon. Objects usually are referred as means for encapsulation, inheritance and polymorphism. A few words regarding encapsulation. Objects are used for structuring source code. Objects contain variables and procedures and protect them providing interface to the contained code. They are used for building a program like a set of interacting pieces of protected code. In the world of C++ objects are indispensable as means for building well structured large and reliable programs. It is curious enough that MFC (Microsoft foundation classes for MS C++) can be thought as a set of libraries presenting collection of procedures to call. Certainly, Modula and Oberon do not need objects for those purposes: they have modules. It is out of doubt that encapsulation provided with modules is at least as good as 'object-oriented' one.

Inheritance and polymorphism are considered instruments necessary to provide abstraction programming. There is opinion that abstraction programming might be considered as useful as structured programming, strong typing and modularity proved to be. For carefully designed programs abstraction programming allows to extend program ability to process new data types just by adding new code and eliminates the necessity of editing code written before. One can define a basic 'abstract' data type and a set of basic 'abstract' procedures dealing with data of that type. Then the basic type definition is refined to produce more specific data type. Inheritance provides compatibility between variables of the basic type and the variables of that 'descendant' type. Since then specific procedures are written. Their names coincide with the names of basic procedures. Polymorphism ensures that during execution of the basic 'abstract' code the proper specific procedure will be called to process data of its own specific 'descendant' type. Doubtlessly the picture looks excellent.

So, abstraction programming is great feature and object-oriented language extensions give us ability to use it. Nevertheless I would not like to use objects but would be glad to have another means for abstraction programming. Why? Tastes differ. And I am afraid that is very much the matter of taste. Commonly speaking, objects are intended to do too much job at once. Therefore their usage is often controversial and brings unnecessary complexity to programming.

On the one hand, objects are tools for programming real or virtual world models built according certain methodology. As far as I know the 'true' object languages with embedded messaging system like SmallTalk have not become very popular. Object-oriented models can be programmed in a variety of languages with more or less comfort. And languages' 'object' extensions are not always needed to accomplish the task. The difference in ease (or difficulty) of programming object-oriented models using languages with or without object extensions is less than one probably could imagine.

On the other hand, objects can be used for abstraction programming. And that their usage is very unlike to the mentioned above programming of object models.

Next to it, objects are widely used (and widely regarded) for the purpose of encapsulation code and structuring programs. And again, that their usage has not much common with their previously described applications. As the means of encapsulation objects are undesirable competitors of modules. And when H. Mössenböck introduced objects in the Oberon-2 language, he had explicitly to address that issue in Object Oriented Programming in Oberon-2 and to explain that Oberon-2 objects are not intended to do the job.

Finally, my personal feeling is that abstraction programming technique (through pointer assignments) provided by objects is rather low-level and is not inherent to the native Modula or Oberon style.

Well, all that remains is to present an idea of how programming abstractions through inheritance and polymorphism can be done in a slightly different way. To tell the truth, no proposal will be of exotic or revolutionary nature. Let's take the Oberon-2 language as our starting point. Inheritance of data types achieved in Oberon-2 language through type extension works good and there is no reason to give it up. Polymorphism in Oberon-2 is achieved through binding procedures to a type. This is what I would like to see changed a little. Please have a look at the following thorough quote from Oberon-2 language report:

"...Record types are extensible, i.e. a record type can be declared as an extension of another record type. In the example

  T0 = RECORD x: INTEGER END
  T1 = RECORD (T0) y: REAL END 
T1 is a (direct) extension of T0 and T0 is the (direct) base type of T1 (see App. A). An extended type T1 consists of the fields of its base type and of the fields which are declared in T1. All identifiers declared in the extended record must be different from the identifiers declared in its base type record(s).

... Type extension (base type) Given a type declaration Tb = RECORD (Ta) ... END, Tb is a direct extension of Ta, and Ta is a direct base type of Tb. A type Tb is an extension of a type Ta (Ta is a base type of Tb) if 1. Ta and Tb are the same types, or 2. Tb is a direct extension of an extension of Ta If Pa = POINTER TO Ta and Pb = POINTER TO Tb, Pb is an extension of Pa (Pa is a base type of Pb) if Tb is an extension of Ta.

... Record and pointer variables have both a static type (the type with which they are declared - simply called their type) and a dynamic type (the type of their value at run time). For pointers and variable parameters of record type the dynamic type may be an extension of their static type. The static type determines which fields of a record are accessible. The dynamic type is used to call type-bound procedures (see 10.2).

... 10.2 Type-bound procedures

Globally declared procedures may be associated with a record type declared in the same module. The procedures are said to be bound to the record type. The binding is expressed by the type of the receiver in the heading of a procedure declaration. The receiver may be either a variable parameter of record type T or a value parameter of type POINTER TO T (where T is a record type). The procedure is bound to the type T and is considered local to it.

  ProcedureHeading  = PROCEDURE [Receiver] IdentDef [FormalParameters]. 
  Receiver   = "(" [VAR] ident ":" ident ")".
If a procedure P is bound to a type T0, it is implicitly also bound to any type T1 which is an extension of T0. However, a procedure P' (with the same name as P) may be explicitly bound to T1 in which case it overrides the binding of P. P' is considered a redefinition of P for T1. The formal parameters of P and P' must match (see App. A). If P and T1 are exported (see Chapter 4) P' must be exported too.

If v is a designator and P is a type-bound procedure, then v.P denotes that procedure P which is bound to the dynamic type of v. Note, that this may be a different procedure than the one bound to the static type of v. v is passed to P's receiver according to the parameter passing rules specified in Chapter 10.1.

If r is a receiver parameter declared with type T, r.P^ denotes the (redefined) procedure P bound to the base type of T. In a forward declaration of a type-bound procedure the receiver parameter must be of the same type as in the actual procedure declaration. The formal parameter lists of both declarations must match.

... Assignment compatible An expression e of type Te is assignment compatible with a variable v of type Tv if one of the following conditions hold: 1. Te and Tv are the same type;

... 3. Te and Tv are record types and Te is an extension of Tv and the dynamic type of v is Tv ; 4. Te and Tv are pointer types and Te is an extension of Tv; ..."

A procedure being bound to an object is called like OBJECT.PROC(PARAMETER). Note, that in the situation when objects are not used for encapsulation this notation is just a well hidden (and probably confusing) way to pass parameter 'OBJECT' to the procedure. Thus there will be no harm if the traditional form of invocation procedure will be used, namely: PROC(OBJECT, PARAMETER). What is next? Polymorphism of procedures with the same name but having different types and/or number of parameters is well-known. It is implemented in Ada, C++ and Modula-3, for instance. It is up to a compiler or to the run-time system to distinguish parameters and decide which actual procedure to call. In our case it would be quite reasonable to limit that kind of polymorphism postulating that the only difference in the parameters allowed is the difference between a base type of a parameter and an extension of its base type. Well, the last thing to invent is how to declare these polymorphic procedures. (Obviously there is not too much to invent.) If one agrees that these procedures are intended to provide abstraction programming, then there is the way to follow. We have to define the 'base' procedure and export it. Any other module will be allowed to import and redefine it. For example:

  MODULE Basic;
  TYPE T0*: RECORD...END;
  BASE PROCEDURE Test*(p:T0); ... END Test;
  END Basic.

  MODULE Extension;
  IMPORT Basic;
  TYPE T1*: RECORD(Basic.T0)...END;
  PROCEDURE Basic.Test*(p:T1); ... END Basic.Test;
  END Extension.

  MODULE Trial;
  IMPORT Basic, Extension;
  VAR V0: Basic.T0; V1: Extension.T1;
  BEGIN
  Basic.Test*(V0);
  (* Invokes Basic.Test *)
  Basic.Test*(V1);
  (* Invokes Extension.Test *)
  Extension.Test*(V1);
  (* Invokes Extension.Test *)
  Extension.Test*(V0);
  (* Error *)
  END Trial.
Basically, that is all I would like to discuss, but there is one more consideration to be mentioned. As you could notice, Oberon-2 allows to extension of any record type. The example above follows it, i.e. any procedure can be redefined to become polymorphic. Probably that may result in program code being rather difficult to read and understand. To be on the safer side we could decide that we need specific 'abstract' modules encapsulating (and exporting) data types and procedures which can be extended and redefined. The notation may be the matter of the only additional keyword preceding "MODULE" keyword, say "BASE" or "ABSTRACT". Thus, abstract code would be easily found and/or identified.
IMPRESSUM: The ModulAtor is an unrefereed journal. Technical papers are to be taken as working papers and personal rather than organizational statements. Items are printed at the discretion of the Editor based upon his judgement on the interest and relevancy to the readership. Letters, announcements, and other items of professional interest are selected on the same basis.

ModulaWare.com website navigator

[ Home | Site_index | Legal | OpenVMS_compiler | Alpha_Oberon_System | ModulAtor | Bibliography | Oberon[-2]_links | Modula-2_links | General interesting book recommendations ]

© (2001) by modulaware.com
Created 03-Nov-2001, last revised 03-Nov-2001