Previous | Table of Contents | Next |
The following sections give an overview of the most important features of Modula-3.
11.2.3.1. Interfaces
One of Modula-2s most successful features is the provision for explicit interfaces between modules. Interfaces are retained with essentially no changes in Modula-3. An interface to a module is a collection of declarations that reveal the public parts of a module; things in the module that are not declared in the interface are private. A module imports the interfaces it depends on and exports the interface (or, in Modula-3, the interfaces) that it implements.
Interfaces make separate compilation type-safe, but it does them an injustice to look at them in such a limited way. Interfaces make it possible to think about large systems without holding the whole system in your head at once.
Programmers who have never used Modula-style interfaces tend to underestimate them, observing, for example, that anything that can be done with interfaces can also be done with C-style include files. This misses the point: Many things can be done with include files that cannot be done with interfaces. For example, the meaning of an include file can be changed by defining macros in the environment into which it is included. Include files tempt programmers into shortcuts across abstraction boundaries. To keep large programs well structured, you either need super-human willpower or proper language support for interfaces.
11.2.3.2. Objects
The better we understand our programs, the bigger the building blocks we use to structure them. After the instruction came the statement, after the statement came the procedure, after the procedure came the interface. The next step seems to be the abstract type.
At the theoretical level, an abstract type is a type defined by the specifications of its operations instead of by the representation of its data. As realized in modern programming languages, a value of an abstract type is represented by an object whose operations are implemented by a suite of procedure values called the objects methods. A new object type can be defined as a subtype of an existing type, in which case the new type has all the methods of the old type and possibly new ones as well (inheritance). The new type can provide new implementations for the old methods (overriding).
Objects were invented in the mid-1960s by the farsighted designers of Simula. Objects in Modula-3 are very much like objects in Simula: They are always references, they have both data fields and methods, and they have single inheritance but not multiple inheritance.
Small examples are often used to get across the basic idea: truck as a subtype of vehicle or rectangle as a subtype of polygon. Modula-3 aims at larger systems that illustrate how object types provide structure for large programs. In Modula-3, the main design effort is concentrated into specifying the properties of a single abstract typea stream of characters or a window on the screen. Then dozens of interfaces and modules are coded that provide useful subtypes of the central abstraction. The abstract type provides the blueprint for a whole family of interfaces and modules. If the central abstraction is well-designed, then useful subtypes can be produced easily, and the original design cost will be repaid with interest.
The combination of object types with Modula-2 opaque types produces something new: the partially opaque type, where some of an objects fields are visible in a scope and others are hidden. Because the committee had no experience with partially opaque types, the first version of Modula-3 restricted them severely, but after a year of experience, it was clear that they were a good thing, and the language was revised to remove the restrictions.
It is possible to use object-oriented techniques even in languages that were not designed to support them by explicitly allocating the data records and method suites. This approach works reasonably smoothly when there are no subtypes; however, it is through subtyping that object-oriented techniques offer the most leverage. The approach works badly when subtyping is needed: Either you allocate the data records for the different parts of the object individually (which is expensive and notationally cumbersome) or you must rely on unchecked type transfers, which is unsafe. Whichever approach is taken, the subtype relations are all in the programmers head: Only with an object-oriented language is it possible to get object-oriented static type-checking.
11.2.3.3. Generics
A generic module is a template in which some of the imported interfaces are regarded as formal parameters to be bound to actual interfaces when the generic is instantiated. For example, a generic hash table module could be instantiated to produce tables of integers, tables of text strings, or tables of any desired type. The different generic instances are compiled independently: The source program is reused, but the compiled code will generally be different for different instances.
To keep Modula-3 generics simple, they are confined to the module level: Generic procedures and types do not exist in isolation, and generic parameters must be entire interfaces.
In the same spirit of simplicity, there is no separate type-checking associated with generics. Implementations are expected to expand the generic and type-check the result. The alternative would be to invent a polymorphic type system flexible enough to express the constraints on the parameter interfaces that are necessary for the generic body to compile. This has been achieved for ML and CLU, but it has not yet been achieved satisfactorily in the Algol family of languages, where the type systems are less uniform. (The rules associated with Ada generics are too complicated for our taste.)
Previous | Table of Contents | Next |