The ModulAtorUbaye's First Independent Oberon-2 & Modula-2 Technical PublicationNr. 84, Nov-2001 |
|
|
|
by Eugene Shcherbatyuk
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 ...
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.
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:
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).
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.
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.
Office of publication.
The Editor of The ModulAtor is Günter Dotzel; he can be reached at
[email deleted due to spam]
ModulaWare.com website navigator
[ Home |
Site_index |
Contact |
Legal |
Buy_products |
OpenVMS_compiler |
Alpha_Oberon_System |
ModulAtor |
Bibliography |
Oberon[-2]_links |
Modula-2_links |
.zel.org Oberon website |
Onduleurs |
General book recommendations ]
© (2001) by modulaware.com
Webdesign by www.otolo.com/webworx,
Created 03-Nov-2001, last revised 03-Nov-2001