home *** CD-ROM | disk | FTP | other *** search
- In spite of its brevity, a description must be complete. Completeness is to
- be achieved within the framework of the chosen abstractions. Limitations
- imposed by particular implementations do not belong to a language
- definition proper. Examples of such restrictions are the maximum values of
- numbers, rounding and truncation errors in arithmetic, and actions taken
- when a program violates the stated rules. It should not be necessary to
- supplement a language definition with a voluminous standards document to
- cover "unforeseen" cases.
-
- But neither should a programming language be a mathematical theory only. It
- must be a practical tool. This imposes certain limits on the terseness of
- the formalism. Several features of Oberon are superfluous from a purely
- theoretical point of view. They are nevertheless retained for practical
- reasons, either for programmers' convenience or to allow for efficient code
- generation without the necessity of complex, "optimizing" pattern matching
- algorithms in compilers. Examples of such features are the presence of
- several forms of repetitive statements, and of standard procedures such as
- INC, DEC, and ODD. They complicate neither the language conceptually nor
- the compiler to any significant degree.
-
- These underlying premises must be kept in mind when comparing Oberon with
- other languages. Neither the language nor its defining document reach the
- ideal; but Oberon approximates these goals much better than its
- predecessors.
-
- A compiler for Oberon has been implemented for the NS32000 processor family
- and is embedded in the Oberon operating environment [8]. The compiler
- requires less than 50 KByte of memory, consists of 6 modules with a total
- of about 4000 lines of source code, and compiles itself in about 15 seconds
- on a workstation with a 25MHz NS32532 processor.
-
- After extensive experience in programming with Oberon, a revision was
- defined and implemented. The differences between the two versions are
- summarised towards the end of the paper. Subsequently, we present a brief
- introduction to (revised) Oberon assuming familiarity with Modula (or
- Pascal), concentrating on the added features and listing the eliminated
- ones. In order to be able to start with a clean slate, the latter are taken
- first.
-
- Features omitted from Modula
-
- Data types
-
- Variant records are eliminated, because they constitute a genuine
- difficulty for the implementation of a reliable storage management system
- based on automatic garbage collection. The functionality of variant records
- is preserved by the introduction of extensible data types.
-
- Opaque types cater for the concept of abstract data type and information
- hiding. They are eliminated as such, because again the concept is covered
- by the new facility of extended record types.
-
- Enumeration types appear to be a simple enough feature to be
- uncontroversial. However, they defy extensibility over module boundaries.
- Either a facility to extend given enumeration types has to be introduced,
- or they have to be dropped. A reason in favour of the latter, radical
- solution was the observation that in a growing number of programs the
- indiscriminate use of enumerations (and subranges) had led to a type
- explosion that contributed not to program clarity but rather to verbosity.
- In connection with import and export, enumerations give rise to the
- exceptional rule that the import of a type identifier also causes the
- (automatic) import of all associated constant identifiers. This exceptional
- rule defies conceptual simplicity and causes unpleasant problems for the
- implementor.
-
- Subrange types were introduced in Pascal (and adopted in Modula) for two
- reasons: (1) to indicate that a variable accepts a limited range of values
- of the base type and to allow a compiler to generate appropriate guards for
- assignments, and (2) to allow a compiler to allocate the minimal storage
- space needed to store values of the indicated subrange. This appeared
- desirable in connection with packed records. Very few implementations have
- taken advantage of this space saving facility, because the additional
- compiler complexity is very considerable. Reason 1 alone, however, did not
- appear to provide sufficient justification to retain the subrange facility
- in Oberon.
-
- With the absence of enumeration and subrange types, the general possibility
- of defining set types based on given element types appeared as redundant.
- Instead, a single, basic type SET is introduced, whose values are sets of
- integers from 0 to an implementation-defined maximum.
-
- The basic type CARDINAL had been introduced in Modula in order to allow
- address arithmetic with values from 0 to 216 on 16-bit computers. With the
- prevalence of 32-bit addresses in modern processors, the need for unsigned
- arithmetic has practically vanished, and therefore the type CARDINAL has
- been eliminated. With it, the bothersome incompatibilities of operands of
- types CARDINAL and INTEGER have disappeared.
-
- Pointer types are restricted to be bound to a record type or to an array
- type.
-
- The notion of a definable index type of arrays has also been abandoned: All
- indices are by default integers. Furthermore, the lower bound is fixed to
- 0; array declarations specify a number of elements (length) rather than a
- pair of bounds. This break with a long standing tradition since Algol 60
- clearly demonstrates the principle of eliminating the inessential. The
- specification of an arbitrary lower bound hardly provides any additional
- expressive power. It represents a rather limited kind of mapping of indices
- which introduces a hidden computational effort that is incommensurate with
- the supposed gain in convenience. This effort is particularly heavy in
- connection with bound checking and with dynamic arrays.
-
- Modules and import/export rules
-
- Experience with Modula over the last eight years has shown that local
- modules were rarely used. Considering the additional complexity of the
- compiler required to handle them, and the additional complications in the
- visiblity rules of the language definition, the elimination of local
- modules appears justified.
-
- The qualification of an imported object's identifier x by the exporting
- module's name M, viz. M.x, can be circumvented in Modula by the use of the
- import clause FROM M IMPORT x. This facility has also been discarded.
- Experience in programming systems involving many modules has taught that
- the explicit qualification of each occurrence of x is actually preferable.
- A simplification of the compiler is a welcome side-effect.
-
- The dual role of the main module in Modula is conceptually confusing. It
- constitutes a module in the sense of a package of data and procedures
- enclosed by a scope of visibility, and at the same time it constitutes a
- single procedure called main program. A module is composed of two textual
- pieces, called the definition part and the implementation part. The former
- is missing in the case of a main program module.
-
- By contrast, a module in Oberon is in itself complete and constitutes a
- unit of compilation. Definition and implementation parts are merged; names
- to be visible in client modules, i.e. exported identifiers, are marked, and
- they typically precede the declarations of objects not exported. A
- compilation generates in general a changed object file and a new symbol
- file. The latter contains information about exported objects for use in the
- compilation of client modules. The generation of a new symbol file must,
- however, be specifically enabled by a compiler option, because it will
- invalidate previous compilations of clients.
-
- The notion of a main program has been abandoned. Instead, the set of
- modules linked through imports typically contains (parameterless)
- procedures. They are to be considered as individually activatable, and they
- are called commands. Such an activation has the form M.P, where P denotes
- the command and M the module containing it. The effect of a command is
- considered - not like that of a main program as accepting input and
- transforming it to output - as a change of state represented by global
- data.
-
- Statements
-
- The with statement of Modula has been discarded. Like in the case of
- imported identifiers, the explicit qualification of field identifiers is to
- be preferred. Another form of with statement is introduced; it has a
- different function and is called a regional guard (see below).
-
- The elimination of the for statement constitutes a break with another long
- standing tradition. The baroque mechanism of Algol 60's for statement had
- been trimmed significantly in Pascal (and Modula). Its marginal value in
- practice has led to its absence from Oberon.
-
- Low-level facilities
-
- Modula makes access to machine-specific facilities possible through
- low-level constructs, such as the data types ADDRESS and WORD, absolute
- addressing of variables, and type casting functions. Most of them are
- packaged in a module called SYSTEM. These features were supposed to be
- rarely used and easily visible through the presence of the identifier
- SYSTEM in a module's import list. Experience has revealed, however, that a
- significant number of programmers import this module quite
- indiscriminately. A particularly seductive trap is the use of Modula's
- type transfer functions.
-
- It appears preferable to drop the pretense of portability of programs that
- import a "standard", yet system-specific module. Type transfer functions
- denoted by type identifiers are therefore eliminated, and the module SYSTEM
- is restricted to providing a few machine-specific functions that typically
- are compiled into inline code. In particular, it does not contain any data
- types, such as ADDRESS and WORD. Individual implementations are free to
- provide appropriate versions of the module SYSTEM, but their facilities do
- not belong to the language definition. The use of SYSTEM declares a program
- to be patently implementation-specific and thereby non-portable.
-
- Concurrency
-
- The system Oberon does not require any language facilities for expressing
- concurrent processes. The pertinent rudimentary features of Modula, in
- particular the coroutine, were therefore not retained. This exclusion is
- merely a reflection of our actual needs within the concrete project, but
- not on the general relevance of concurrency in programming.
-
- Features introduced in Oberon
-
- In contrast to the number of eliminated features, there are only a few new
- ones. The important new concepts are type extension and type inclusion.
- Furthermore, open arrays may have several dimensions (indices), whereas in
- Modula they were confined to a single dimension.
-
- Type extension
-
- The most important addition is the facility of extended record types. It
- permits the construction of new types on the basis of existing types, and
- establishes a certain degree of compatibility between the new and old
- types. Assuming a given type
-
- T = RECORD x, y: INTEGER END
-
- extensions may be defined which contain certain fields in addition to the
- existing ones. For example
-
- T0 = RECORD (T) z: REAL END
- T1 = RECORD (T) w: LONGREAL END
-
- define types with fields x, y, z and x, y, w respectively. We define a type
- declared by
-
- T' = RECORD (T) <field definitions> END
-
- to be a (direct) extension of T, and conversely T to be the (direct) base
- type of T'. Extended types may be extended again, giving rise to the
- following definitions:
-
- A type T' is an extension of T, if T' = T or T' is a direct extension of an
- extension of T. Conversely, T is a base type of T', if T = T' or T is the
- direct base type of a base type of T'. We denote this relationship by T' .
- T.
-
- The rule of assignment compatibility states that values of an extended type
- are assignable to variables of their base types. For example, a record of
- type T0 can be assigned to a variable of the base type T. This assignment
- involves the fields x and y only, and in fact constitutes a projection of
- the value onto the space spanned by the base type.
-
- It is important to allow modules which import a base type to be able to
- declare extended types. In fact, this is probably the normal usage.
-
- This concept of extensible data type gains importance when extended to
- pointers. It is appropriate to say that a pointer type P' bound to T'
- extends a pointer type P, if P is bound to a base type T of T', and to
- extend the assignment rule to cover this case. It is now possible to form
- data structures whose nodes are of different types, i.e. inhomogeneous data
- structures. The inhomogeneity is automatically (and most sensibly) bounded
- by the fact that the nodes are linked by pointers of a common base type.
-
- Typically, the pointer fields establishing the structure are contained in
- the base type T, and the procedures manipulating the structure are defined
- in the same (base) module as T. Individual extensions (variants) are
- defined in client modules together with procedures operating on nodes of
- the extended type. This scheme is in full accordance with the notion of
- system extensibility: new modules defining new extensions may be added to a
- system without requiring a change of the base modules, not even their
- recompilation.
-
- As access to an individual node via a pointer bound to a base type provides
- a projected view of the node data only, a facility to widen the view is
- necessary. It depends on the ability to determine the actual type of the
- referenced node. This is achieved by a type test, a Boolean expression of
- the form
-
- t IS T' (or p IS P')
-
- If the test is affirmative, an assignment t' := t (t' of type T') or p' :=
- p (p' of type P') should be possible. The static view of types, however,
- prohibits this. Note that both assignments violate the rule of assignment
- compatibility. The desired assignment is made possible by providing a type
- guard of the form
-
- t' := t(T') (p' := p(P'))
-
- and by the same token access to the field z of a T0 (see previous examples)
- is made possible by a type guard in the designator t(T0).z. Here the guard
- asserts that t is (currently) of type T0. In analogy to array bound checks
- and case selectors, a failing guard leads to program abortion.
-
- Whereas a guard of the form t(T) asserts that t is of type T for the
- designator (starting with) t only, a regional type guard maintains the
- assertion over an entire sequence of statements. It has the form
-
- WITH t: T DO StatementSequence END
-
- and specifies that t is to be regarded as of type T within the entire
- statement sequence. Typically, T is an extension of the declared type of t.
- Note that assignments to t within the region therefore require the assigned
- value to be (an extension) of type T. The regional guard serves to reduce
- the number of guard evaluations.
-
- As an example of the use of type tests and guards, consider the following
- types Node and Object defined in a module M:
-
- TYPE Node = POINTER TO Object;
- Object = RECORD key, x, y: INTEGER;
- left, right: Node
- END
-
- Elements in a tree structure anchored in a variable called root (of type
- Node) are searched by the procedure element defined in M.
-
- PROCEDURE element(k: INTEGER): Node;
- VAR p: Node;
- BEGIN p := root;
- WHILE (p # NIL) & (p.key # k) DO
- IF p.key < k THEN p := p.left ELSE p := p.right END
- END ;
- RETURN p
- END element
-
- Let extensions of the type Object be defined (together with their pointer
- types) in a module M1 which is a client of M:
-
- TYPE Rectangle = POINTER TO RectObject;
- RectObject = RECORD (Object) w, h: REAL END ;
- Circle = POINTER TO CircleObject;
- CircleObject = RECORD (Object) rad: REAL; shaded: BOOLEAN END
-
- After the search of an element, the type test is used to discriminate
- between the different extensions, and the type guard to access extension
- fields. For example:
-
- p := M.element(K);
- IF p # NIL THEN
- IF p IS Rectangle THEN ... p(Rectangle).w ...
- ELSIF (p IS Circle) & ~p(Circle).shaded THEN ... p(Circle).rad ...
- ELSIF ...
-
- The extensibility of a system rests upon the premise that new modules
- defining new extensions may be added without requiring adaptations nor even
- recompilation of the existing parts, although components of the new types
- are included in already existing data structures.
-
- The type extension facility not only replaces Modula's variant records, but
- represents a type-safe alternative. Equally important is its effect of
- relating types in a type hierarchy. We compare, for example, the Modula
- types
-
- T0' = RECORD t: T; z: REAL END ;
- T1' = RECORD t: T; w: LONGREAL END
-
- which refer to the definition of T given above, with the extended Oberon
- types T0 and T1 defined above. First, the Oberon types refrain from
- introducing a new naming scope. Given a variable r0 of type T0, we write
- r0.x instead of r0.t.x as in Modula. Second, the types T, T0', and T1' are
- distinct and unrelated. In contrast, T0 and T1 are related to T as
- extensions. This becomes manifest through the type test, which asserts that
- variable r0 is not only of type T0, but also of base type T.
-
- The declaration of extended record types, the type test, and the type guard
- are the only additional features introduced in this context. A more
- extensive discussion is provided in [2]. The concept is very similar to the
- class notion of Simula 67 [3], Smalltalk [4], Object Pascal [5], C++ [6],
- and others, where the properties of the base class are said to be inherited
- by the derived classes. The class facility stipulates that all procedures
- applicable to objects of the class be defined together with the data
- definition. This dogma stems from the notion of abstract data type, but it
- is a serious obstacle in the development of large systems, where the
- possibility to add further procedures defined in additional modules is
- highly desirable. It is awkward to be obliged to redefine a class solely
- because a method (procedure) has been added or changed, particularly when
- this change requires a recompilation of the class definition and of all its
- client modules.
-
- We emphasise that the type extension facility - although gaining its major
- role in connection with pointers to build heterogeneous, dynamic data
- structures as shown in the example above - also applies to statically
- declared objects used as variable parameters. Such objects are allocated in
- a workspace organized as a stack of procedure activation records, and
- therefore take advantage of an extremely efficient allocation and
- deallocation scheme.
-
- In Oberon, procedure types rather than procedures (methods) are connected
- with objects in the program text. The binding of actual methods (specific
- procedures) to objects (instances) is delayed until the program is
- executed. The association of a procedure type with a data type occurs
- through the declaration of a record field. This field is given a procedure
- type. The association of a method - to use Smalltalk terminology - with an
- object occurs through the assignment of a specific procedure as value to
- the field, and not through a static declaration in the extended type's
- definition which then "overrides" the declaration given in the base type.
- Such a procedure is called a handler. Using type tests, the handler is
- capable of discriminating among different extensions of the record's
- (object's) base type. In Smalltalk, the compatibility rules between a class
- and its subclasses are confined to pointers, thereby intertwining the
- concepts of access method and data type in an undesirable way. In Oberon,
- the relationship between a type and its extensions is based on the
- established mathematical concept of projection.
-
- In Modula, it is possible to declare a pointer type within an
- implementation module, and to export it as an opaque type by listing the
- same identifier in the corresponding definition module. The net effect is
- that the type is exported while all its properties remain hidden (invisible
- to clients). In Oberon, this facility is generalized in the sense that the
- selection of the record fields to be exported is arbitrary and includes the
- cases all and none. The collection of exported fields defines a partial
- view - a public projection - to clients.
-
- In client modules as well as in the module itself, it is possible to define
- extensions of the base type (e.g. TextViewers or GraphViewers). Of
- importance is also the fact that non-exported components (fields) may have
- types that are not exported either. Hence, it is possible to hide certain
- data types effectively, although components of (opaquely) exported types
- refer to them.
-
- Type inclusion
-
- Modern processors feature arithmetic operations on several number formats.
- It is desirable to have all these formats reflected in the language as
- basic types. Oberon features five of them:
-
- LONGINT, INTEGER, SHORTINT (integer types)
- LONGREAL, REAL (real types)
-
- With the proliferation of basic types, a relaxation of compatibility rules
- among them becomes almost mandatory. (Note that in Modula the numeric types
- INTEGER, CARDINAL, and REAL are incompatible). To this end, the notion of
- type inclusion is introduced: a type T includes a type T', if the values of
- type T' are also values of type T. Oberon postulates the following
- hierarchy:
-
- LONGREAL J REAL J LONGINT J INTEGER J SHORTINT
-
- The assignment rule is relaxed accordingly: A value of type T' can be
- assigned to a variable of type T, if T' is included in T (or if T' extends
- T), i.e. if T J T' or T' . T. In this respect, we return to (and extend)
- the flexibility of Algol 60. For example, given variables
-
- i: INTEGER; k: LONGINT; x: REAL
-
- the assignments
-
- k := i; x := k; x := 1; k := k+i; x := x*10 + i
-
- conform to the rules, whereas the statements i := k; k := x are not
- acceptable. x := k may involve truncation.
-
- The presence of several numeric types is evidently a concession to
- implementations which can allocate different amounts of storage to
- variables of the different types, and which thereby offer an opportunity
- for storage economization. This practical aspect should - with due respect
- for mathematical abstraction - not be ignored. The notion of type inclusion
- minimises the consequences for the programmer and requires only few
- implicit instructions for changing the data representation, such as sign
- extensions and integer to floating-point conversions.
-
- .
-
- Differences between Oberon and Revised Oberon
-
- A revision of Oberon was defined after extensive experience in the use and
- implementation of the language. Again, it is characterized by the desire to
- simplify and integrate. The differences between the original version [7]
- and the revised version [9] are the following:
-
- 1. Definition and implementation parts of a module are merged. It appeared
- as desirable to have a module's specification contained in a single
- document, both from the view of the programmer and the compiler. A
- specification of its interface to clients (the definition part) can be
- derived automatically. Objects previously declared in the definition part
- (and repeated in the implementation part), are specially marked for export.
- The need for a structural comparison of two texts by the compiler thereby
- vanishes.
-
- 2. The syntax of lists of parameter types in the declaration of a
- procedure type is the same as that for regular procedure headings. This
- implies that dummy identifiers are introduced; they may be useful as
- comments.
-
- 3. The rule that type declarations must follow constant declarations, and
- that variable declarations must follow type declarations is relaxed.
-
- 4. The apostrophe is eliminated as a string delimiter.
-
- 5. The relaxed parameter compatibility rule for the formal type ARRAY OF
- BYTE is applicable for variable parameters only.
-
- Summary
-
- The language Oberon has evolved from Modula-2 and incorporates the
- experiences of many years of programming in Modula. A significant number of
- features have been eliminated. They appear to have contributed more to
- language and compiler complexity than to genuine power and flexibility of
- expression. A small number of features have been added, the most
- significant one being the concept of type extension.
-
- The evolution of a new language that is smaller, yet more powerful than its
- ancestor is contrary to common practices and trends, but has inestimable
- advantages. Apart from simpler compilers, it results in a concise defining
- document [9], an indispensible prerequisite for any tool that must serve in
- the construction of sophisticated and reliable systems.
-
- Acknowlegement
-
- It is impossible to explicitly acknowledge all contributions of ideas that
- ultimately simmered down to what is now Oberon. Most came from the use or
- study of existing languages, such as Modula-2, Ada, Smalltalk, and Cedar,
- which often taught us how not to do it. Of particular value was the
- contribution of Oberon's first user, J. Gutknecht. The author is grateful
- for his insistence on the elimination of dead wood and on basing the
- remaining features on a sound mathematical foundation. And last, thanks go
- to the anonymous referee who very carefully read the manuscript and
- contributed many valuable suggestions for improvement.
-
- References
-
- 1. N. Wirth. Programming in Modula-2. Springer-Verlag, 1982.
-
- 2. N. Wirth. Type Extensions. ACM Trans. on Prog. Languages and Systems,
- 10, 2 (April 1988) 204-214.
-
- 3. G. Birtwistle, et al. Simula Begin. Auerbach, 1973.
-
- 4. A. Goldberg, D. Robson. Smalltalk-80: The Language and its
- Implementation. Addison-Wesley, 1983.
-
- 5. L. Tesler. Object Pascal Report. Structured Language World, 9, 3
- (1985), 10-14.
-
- 6. B. Stroustrup. The Programming Language C++. Addison-Wesley, 1986.
-
- 7. N. Wirth. The programming language Oberon. Software - Practice and
- Experience, 18, 7 (July 1988), 671-690.
-
- 8. J. Gutknecht and N. Wirth. The Oberon System. Software - Practice and
- Experience, 19, (1989)
-
- 9. N. Wirth. The programming language Oberon (Revised Report). (companion
- paper)
-
- File: ModToOberon2.Doc / NW 25.8.89
-
-