The ModulaTor logo 7KB

The ModulaTor

Oberon-2 and Modula-2 Technical Publication

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    [Any browser]

Webdesign by www.otolo.com/webworx, 14-Jul-1998