The ModulaTor
Erlangen's First Independent Modula_2 Journal! Nr. 2, Mar-1994
__________________________________________________________________________________________________
Proposal for Object Oriented Extension of [ISO] Modula-2
\251 (1994) by Elmar Henne and Albert Wiedemann
1. Introduction
This proposal is based on a former [unpublished] paper entitled "Minimal Object
Oriented Extension for Modula-2" (D185) and incorporates the decisions made
at [the ISO Modula-2 working group meeting in] Langley [Jun-1993] as well as
the results of the discussions in the German subgroup DIN NI22.13. This
proposal describes a "minimal model" in the sense that it contains only those
features which the DIN group considered as necessary to start with. The
proposal is open to additions (like e. g. multiple inheritance or genericity), i. e. it
is designed in a way that these additions may be made without changes to the
basic model.
2. Classification of this proposal
The following list tries to give a general classification scheme and to classify this
model accordingly.
- Arity of inheritance: single inheritance
- Access to objects: references and values
- Visibility modes: three modes via export rules
- Constructors / Destructors: without parameters (module initialization /
finalization)
- Object allocation / deallocation: via NEW / DISPOSE
- Type inquiries: type test and type case
- Class syntax: based on modules
- Garbage collection: optional, see appendix
- Standard root object: none
- Genericity: no
- Operators: no
- Overloading: no
3. Classes
3.1 A new kind of modules is introduced, called "class modules". Class modules
are a new kind of type - the type of reference based objects - and can be used
as any other type. Class modules are declared in implementation or program
modules. Class definitions may occur in definition modules, the defined
methods have to be declared in a class implementation module in the according
implementation module. If no methods need to be declared, the class
implementation module may be omitted.
A class declaration has nearly the same syntax as a module declaration and
declares the features of this class. Constant and type declarations have their
usual meaning, variable declarations declare attributes, and procedure
declarations declare methods. Protection is allowed. In general: no restrictions
pare made for the current module rules (e.g. protection,) to avoid unnecessary
special rules as long as the current rules do not introduce complications.
A class definition has the same restrictions as a definition module (i. e. method
definitions instead of method declarations) and defines the external interface of
a class.
3.2 Inside a method the identifier "SELF" denotes the object for which the
method is called. "SELF" acts like an automatic first value parameter (i.e. using
"SELF" as a name for a paramter or a local variable in a method is automatically
prohibited by the existing scope rules) and is always an object reference.
3.3 Types for value based objects are derived from class modules (see 8).
3.4 Example
DEFINITION MODULE Module1;
(* class definition for "Class1": *)
CLASS MODULE Class1;
EXPORT QUALIFIED
firstState, State,
attribute1, attribute2, Meth1; (* public visible *)
TYPE
State = (starting, active, passive);
CONST
firstState = starting;
VAR (* defines attributes *)
attribute1: State;
attribute2: REAL;
attribute3: INTEGER; (* visible only in methods
of this and all
descendant classes. *)
(* method definitions: *)
PROCEDURE Meth1 (i: INTEGER);
PROCEDURE Meth2 (r: REAL); (* like "attribute3" *)
END Class1;
TYPE
ValClass1 = VALUE OF Class1;
END Module1.
IMPLEMENTATION MODULE Module1;
(* class declaration for "Class1": *)
CLASS MODULE Class1;
EXPORT QUALIFIED
Meth3; (* visible in "Module1" *)
VAR
attribute4: INTEGER; (* private attribute, visible only *)
(* within this class *)
(* method declarations: *)
PROCEDURE Meth1 (i: INTEGER);
BEGIN
attribute1 := active;
INC (attribute4, i);
Proc1 (SELF);
END Meth1;
PROCEDURE Meth2 (r: REAL);
:
END Meth2;
PROCEDURE Meth3 (i: INTEGER); (* internal method *)
BEGIN
Meth1 (i);
DEC (attribute4);
END Meth3;
END Class1;
PROCEDURE Proc1 (c: Class1) ;
:
END Proc1;
:
:
:
END Module1.
Rationale: 3R1 The syntax is based on modules, as this allows to define a lot of
OO features by using already existing rules. Thus, most of the OO features
need only very few further explanation. This has also the nice effect of
minimalizing the number of new keywords. Dynamic modules fully explain the
interaction of exceptions and constructors / destructors for local value objects,
the semantics for reference based objects follows directly from these rules.
3R2 Class modules always need to have a type name. The used syntax avoids
implicit class declarations; a class declaration based on the normal type
declaration syntax (e.g. derived from record declarations) needs special
prohibition rules.
3R3 Moreover, though classes are types in the sense of language definition, an
object is used in a way that differs from using other variables. An object has
psome "live of its own", and acts more like a dynamic module instance than a
record variable. So the module syntax seems more appropriate than syntax
based on records.
3R4 A way is needed inside a method to have access to the calling object as a
whole (e.g. for passing it as parameter to other methods, assignig it to global
variables etc.). Declaring "SELF" as a pervasive identifier (constant or function)
would lead to very complicated (and strange) visiblity and meaning rules. If
"SELF" is declared as the first identifier in the scope of each method, the
existing (scope) rules give a complete explanation for the use of "SELF".
3R5 The only "true" objects are reference based objects, as value based objects
do not allow polymorphism. In nearly all OO languages value based objects are
introduced as more efficient but restricted objects - like items for cases where
polymorphism is not needed but features like automatic data initialization (by
constructors) are desired. Therefore the main goal of the proposal are the
reference based objects, value based objects are (even by syntax) treated as
"additional sugar".
4. Visibility
The visibility of entities of a class module (constants, types, attributes, and
methods) is governed by the normal import / export rules. Entities exported from
a class module definition are visible and accessible everywhere from outside of
the object (public). Not exported entities are only visible inside the class and
inside its descendant classes (family, see 6. Inheritance). Not exported entities
from a class declaration are private to this class, they are not visible in
descendant classes (private). This avoids some kind of name conflicts.
As with other modules, export can be qualified or unqualified. The qualification is
resolved by using the class name (e. .g "object. Class1. Meth1 (...)").
For the visibility inside a class module, the normal import rules apply.
Rational: 4R1 Instead of introducing new visibility rules, we use the existing
rules for modules. They reflect also the types of users of a class. For client
users, public entities are visible , others are hidden (like as with nornal
modules). Inheritance can be modelled like opening an inner scope (e.g. like
nested modules with automatc export, see 5.3), this leads directly to the
distinction between family and private attributes.
4R2 Family entities are very usefull for abstract classes.
4R3 Private attributes can be implemented without runtime overhead by using
symbolic offsets.
5. Inheritance
5.1 Class modules can inherit from at mostly one other class module (no
multiple inheritance). Inheritance is specified by the new keyword "INHERIT" in
a way similar to import / export. The inherit statement must be placed before the
import statement(s).
5.2 If class B inherits from class A, B will become a subtype of A. Reference
based objects of type B will then be assignment compatible to variables of type
A. Therefore, the static type of an object variable (the one used to declare the
variable) may be different from the dynamic type of the object that is referenced
by this variable, the dynamic type might be a subtype.
5.3 Objects of class B contain all entities of A plus all the additional entities of B.
Class B creates a new module scope arround class A with automatic export of
all entities of A into this outer scope.
5.4 Methods of the ancestor class A can be overridden in the descendant class
B. pOverridden methods preserve the method interface but replace the
implementation. Method calls of objects will always refer to the method
implementation according to the dynamic type of the object. Overriding of an
existing method has to be flagged by the new keyword "OVERRIDE". If a class
definition exists, "OVERRIDE" has to be used in both, definition and declaration.
5.5 Overriding methods can call the overridden method by use of explicit
qualification with the ancestor class.
Example:
DEFINITION MODULE Module2;
FROM Module1 IMPORT Class1;
:
CLASS MODULE Class2;
INHERIT Class1; (* inherits from Class1 *)
(* alternate keyword: EXTEND *)
EXPORT QUALIFIED
attribute21, Meth21; (* common visible *)
VAR
attribute21: INTEGER; (* additional attribute *)
PROCEDURE Meth21 (): INTEGER; (* additional method *)
OVERRIDE PROCEDURE Meth1 (i: INTEGER);
(* redefinition of Class1. Meth1, the keyword OVERRIDE assures that
1. there exists a method to be overridden
2. no method can be overridden by accidence
*)
END Class2;
:
END Module2.
IMPLEMENTATION MODULE Module2;
:
CLASS MODULE Class2;
PROCEDURE Meth21 (): INTEGER;
BEGIN
RETURN attribute21;
END Meth21 ;
PROCEDURE Meth22 (VAR i: INTEGER); (* additional method *)
BEGIN
INC (i, attribute21);
END Meth22 ;
PROCEDURE Meth1 (i: INTEGER);
BEGIN
Class1. Meth1 (i); (* "Class1." indicates a call to the *)
(* overridden procedure *)
Meth22 (i);
END Meth1;
END Class2;
:
END Module2.
Rationale: 5R1 No multiple inheritance is introduced for simplicity, but could be
added (e.g. like in the paper of Markus Klingspor [as published in The
ModulaTor, issue Feb-1994) without invalidating this model.
5R2 Explicit overriding avoids overriding by chance and makes sources more
readable.
5R3 Automatic export avoids problems with unintented redefinitions.
6. Abstract Classes
Abstract classes are used to define interface descriptions, no object of an
abstract class can be instantiated. Within an abstract class, abstract methods
may be defined, they do not have an implementation, but have to be overridden
in descendant classes. Abstract classes and methods are flagged with the
keyword ABSTRACT. Abstract classes must have a class definition, they may
have a class declaration (e.g if they contain none-abstract methods).
DEFINITION MODULE Drawing;
IMPORT Quickdraw;
ABSTRACT CLASS MODULE DrawObject;
EXPORT
:
:
(* draw methods *)
PROCEDURE DrawTo (x, y: REAL);
PROCEDURE MoveTo (x, y: REAL);
PROCEDURE DrawRelative (x, y: REAL);
PROCEDURE MoveRelative (x, y: REAL);
:
(* character drawing *)
ABSTRACT PROCEDURE SetCharSize (size: CARDINAL);
ABSTRACT PROCEDURE Write (str: ARRAY OF CHAR);
(* for subscribers only, not exported *)
ABSTRACT PROCEDURE DoDrawing (x, y: INTEGER; toDraw: BOOLEAN);
END(*CLASS*);
(* procedures to create new objects; the user need not know about
the object hierarchy
*)
PROCEDURE CreateScreenObject (port: Quickdraw. GrafPtr): DrawObject;
PROCEDURE CreatePlotterObject (fileName: ARRAY OF CHAR): DrawObject;
END Drawing.
Rationale: 6R1 Abstract classes are used to model abstract data types and
interfaces. They guarantee that no object of this (incomplete) class can be
instantiated. Abstract methods need no (empty default) implementation. 6R2
Explicit declaration of a class to be abstract improves readability and allows
additional compile time checking.
7. Reference based Objects
7.1 Reference based objects are allocated and instantiated by a call to NEW
(var [, type]). A reference to the new object is stored in the first variable passed
to NEW. The type of the new object is either the static class type of the variable
por the class type specified as an optional second parameter. The optional type
parameter must be assignment compatible to the type of the variable (cf. section
5, Inheritance).
7.2 Attributes and methods of an object are accessed via selection syntax. The
selection will automatically dereference the object reference (no "^" is needed).
7.3 Objects are destroyed by a call to DISPOSE. The dynamic type of the object
(and the space it occupies) is detected by the runtime system.
7.4 The assignment compatibility rules are weakened for reference based
objects. Reference based objects are not only assignment compatible to objects
of the same class but also to objects of an ancestor class.
7.5 All rules that apply for variables of pointer type also apply for reference
based objects:
- "NIL" and address type variables are assignment compatible to a reference
based object.
- The comparison operators "=" (equalitiy) and "<>" (inequality ) are overloaded
for reference based objects. They test the equality of the reference, not of the
object as a whole. A reference based object may be compared to "NIL".
- Opaque types may be implemented as reference based objects.
- SIZE, TSIZE and CAST act as with pointers.
MODULE ModuleX;
:
VAR
obj1: Class1;
i: INTEGER;
r: REAL;
BEGIN
NEW (obj1);(* space for obj1 is allocated, and it is initialized *)
obj1. Meth1 (i); (* call to Meth1 *)
r := obj1. attribute2; (* access to attribute2 *)
DISPOSE (obj1); (* space for obj1 is deallocated *)
NEW (obj1, Class2); (* obj1 is allocated and initialized with *
)
(* the dynamic type Class2 *)
END ModuleX.
Rationale: 7R References are the default because this is the classic use of
objects. Polymorphism is only possible with references (cf. 3R3).
8. Value based Objects
8.1 Value based Objects are allocated statically like records on stack, as global
variables or as part of other data structures. They are instantiated as either the
inititialization part of the module is started, or at procedure entry (like dynamic
modules), or at a call to NEW.
8.2 Value based Objects are destroyed at procedure exit (objects on stack) or
during termination.
8.3 The type of a value based object is always static, assignment compatibility is
only for identical types (no projection!). The assignment of a value based object
to a (previously instantiated) reference based object of the same class is not
possible (by using "SYSTEM. ADR" an assignment to a object reference
variable is possible).
8.4 Comparison of value based objects is not possible. SIZE / TSIZE of value
based classes are not available at compile time, so these functions must not be
used in constant expressions if applied to value based classes.
MODULE ModuleY;
:
VAR
obj: Class1;
:
PROCEDURE P1;
VAR
obj1,
obj2: VALUE OF Class1; (* constructor(s) called at procedure entry *)
i: INTEGER;
r: REAL;
BEGIN (* obj1 and obj2 are instantiated *)
obj1. Meth1 (i); (* call to Meth1 *)
r := obj1. attribute2; (* access to attribute2 *)
obj2 := obj1; (* assigns attributes of obj1 to obj2 *)
END P1; (* Destructors are called for obj1 and obj2 *)
END ModuleY.
Rationale: 8R1 Value based objects are of practical interest, as they can be
used with less memory overhead in all situations where polimorphism is not
needed.
8R2 Value based classes allow declaration of data types with automatic data
initialization.
8R3 No projection is provided, as it needs additional assignment rules but is
very seldom needed in practice.
9. Constructors / Destructors
9.1 The initialization part of a class module plays the role of a constructor, i. e. it
is executed at the time of object creation. The finalization part of a class module
plays the role of a destructor, i. e. it is executed at the time of object deletion.
9.2 For inheritance chains, the normal rules for execution order of inner modules
apply. A parent class is treated in this context as if it would have been expanded
as a nested module within the descendant class (i. e. its constructor is executed
before the constructor of the descendant class, the destructor is executed after
the destructor of the descendant class).
9.3 A value based object variable is treated (accoring to the module rules) as if
its class would have been expanded as a local or dynamic module at the place
of the variable declaration.
CLASS MODULE Class1;
:
BEGIN
attribute1 := 0; (* do necessary initializations *)
attribute2 := 1.0;
attribute3 := 0;
FINALLY
WHILE attribute3 > 0 DO
(* e. g. free allocated space *)
END(*WHILE*);
END Class1;
Rationale: 9R1 Constructors and destructors are needed for the same reasons
as initialization and termination for modules.
9R2 Value based objects with constructors allow to declare variables with
automatic data initialization.
10. Type Inquiries
Three new features are introduced for testing and converting the type of an
object.
10.1 The new pervasive function "ISMEMBER" is introduced to check for the
dynamic type of an object. "ISMEMBER" takes two parameters, that may be
either object variables (designator) or class types. It returns true, iff the
(dynamic) type of the first parameter is a descendant or equal to the (dynamic)
type of the second parameter. Given that the first parameter is an object and the
second is a class type, "ISMEMBER" returns true, if the object is assignment
compatible to the specified class.
10.2 The standard function "VAL" is extended for conversion of objects to any
class from the inheritance chain of the objects dynamic class. A mandatory
exception occurs (and shall be raised) if the dynamic type of the given object is
not passignment compatible to the specified class.
10.3 A new kind of case statement is introduced (type case, keyword
"TYPECASE") to provide an efficient combination of type test and type
conversion. The case selector is a (qualified) identifier of a reference based
object, the case labels are class types. Only single labels are allowed. Executing
the case statement, the dynamic type of an object is checked against the type
labels and the first matching label (assignment compatibility) is selected. Inside
the selected statement the selection object identifier will have the static type of
the case label. An optional ELSE clause may be specified. If the selection object
does not match with any class specified as variant (especially if the object
variable contains the value "NIL"), the ELSE clause is selected. If no ELSE
clause is specified and no variant is selected, a mandatory exception occurs
and shall be raised.
Examples:
The following examples rely on a class "DrawObject" with its descendants
"DrawScreen", "DrawPrinter", and "DrawPlotter". Each of the three subclasses
may have descendants of their own.
VAR
draw: DrawObject;
print: DrawPrinter;
:
IF ISMEMBER (draw, DrawPrinter) (* if the dynamic type of "draw" *)
THEN (* is assignment compatible to *)
DrawForEveryPage; (* "DrawPrinter", do special *)
ELSE (* actions *)
DrawOnce;
END(*IF*);
print := VAL (DrawPrinter, draw);
(* raises an exception, if draw ins not of class "DrawPrinter"
or a descendant.
*)
TYPECASE draw OF
DrawScreen:
draw. CalcScreenSize;
| DrawPrinter:
draw. CalcPrinterSize;
| DrawPlotter:
draw. CalcPlotterSize;
ELSE
(* may not occur in this example *)
END(*CASE*);
Rationale: 10R1 Though type inquiries are stated superfluous by purists of
object oriented programming, there are enough situations in practice, where
they have proved to be very usefull. Three kinds of type inquiries are widely
used.
10R2 A means has to be provided to check the dynamic type of an object. A
(pervasive) function ISMEMBER is sufficient and has less implications than the
former suggested TYPEOF together with the new operator meaning.
10R3 The symmetrie of the parameters allows more flexibility, e.g. test for type
equality by using "ISMEMBER (x, A) AND ISMEMBER (A, x)".
10R4 It should be possible to assign a given object to an object variable of a
descendant class (if the dynamic type allows it) to get access to the full
functionality of this class. As "VAL" is used in similar situations, no new function
is introduced.
10R5 The TYPECASE - statement is an efficient combination if the two former
features without the need for auxiliary variables.
Appendix 1: Garbage Collection
One possibility for garbage collection in Modula-2 is to use so called
"Conservative Garbage Collectors". These collectors do not require any
language support, they can be even used for languages like C. Garbage
collection must therefore not be specified by the language definition, it can be
left open as an implementation issue.
The basic idea for conservative garbage collection is to treat every bit pattern
that could be a pointer in fact as a valid pointer and not to reclaim the storage
where it is pointing to. This will normally lead to a non optimal memory usage as
not all unused blocks can be always reclaimed, but even with other garbage
collection techniques there will be always some memory overhead compared to
explicit deallocation. The article "Space Efficient Conservative Garbage
Collection" in ACM SIGPLAN Notices volume 28, number 6, June 93, pages 197
- 204, shows that conservative garbage collectors can be implemented in a way
which makes them a promising alternative to conventional garbage collectors.
One advantage of this approach is also, that the problem of coroutine
workspaces (when are they invalidated?) would be solved without any change
to the language.
Appendix 2: Concrete Syntax
The following extensions have to be made to the concrete syntax (paragraph 10
of annex C of CD 10514-2 [second committee draft of ISO Modula-2, Dec-1993]
describes the new syntax items. Other paragraphs list the productions, where
the new items are merged into the existing syntax and refer directly to Annex C
of CD 10514-2):
C.2.2 Definitions
definition =
"CONST", {constant declaration, semicolon} |
"TYPE", {type definition, semicolon} |
"VAR", {variable declaration, semicolon} |
procedure heading, semicolon |
class definition, semicolon ;
C.2.3 Declarations
declaration =
"CONST", {constant declaration, semicolon} |
"TYPE", {type declaration, semicolon} |
"VAR", {variable declaration, semicolon} |
procedure declaration, semicolon |
class declaration, semicolon |
local module declaration, semicolon;
C.3 Types
type denoter = type identifier | class identifier | new type ;
C.3.2 New Types
new type =
new ordinal type | set type | packedset type | pointer type
|
procedure type | array type | record type | value object
type ;
C.5 Statements
statement =
empty statement | assignment statement | procedure call |
retry statement | with statement | if statement |
case statement | while statement | repeat statement |
loop statement | exit statement | for statement |
typecase statement ;
C.10 Classes
C.10.1 Class Definitions
class definition =
[ "ABSTRACT" ], "CLASS", "MODULE", class identifier,
[ protection ], semicolon,
[ "INHERIT", "FROM", class identifier, semicolon ],
import lists,
[qualified export],
entity definitions,
"END", class identifier, semicolon ;
entity definitions = {entity definition } ;
entity definition =
"CONST", {constant declaration, semicolon} |
"TYPE", {type definition, semicolon} |
"VAR", {variable declaration, semicolon} |
method heading, semicolon |
abstract method heading, semicolon ;
method heading = [ "OVERRIDE" ], procedure heading ;
abstract method heading = "ABSTRACT", procedure heading ;
class identifier = qualified identifier
C.10.2 Class Declarations
class declaration =
"CLASS", "MODULE", class identifier, [ protection ], semicolon,
[ "INHERIT", "FROM", class identifier, semicolon ],
import lists,
[qualified export],
entity declarations ,
"END", class identifier, semicolon ;
entity declarations = {entity declaration } ;
entity declaration =
"CONST", {constant declaration, semicolon} |
"TYPE", {type declaration, semicolon} |
"VAR", {variable declaration, semicolon} |
method declaration, semicolon ;
method declaration = [ "OVERRIDE" ], procedure declaration ;
C.10.3 Value Objects
value object type = "VALUE", "OF", class identifier ;
________________________________________________________________________
Editors notes: All text enclosed in square brakets (except in Appendix 2) was
inserted by the editor.
The authors can be contacted directly by Email:
Elmar Henne, internet: GER.XSE0109@applelink.apple.com
Albert Wiedemann, internet: 100040.246@compuserve.com
The ModulaTor Forum
This issue contains the second article which deals with an OOE of ISO
Modula-2.
Further proposals, letters to the editor and critique are welcome.
Adress all correspondance to the Editor of The ModulaTor, c/o ModulaWare,
see Impressum below.
________________________________________________________________
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 Guenter Dotzel; he can be reached
by tel/fax: [removed due to abuse] or by
mailto:[email deleted due to spam]
ModulaWare home page
The ModulaTor download
![]()