• Ingen resultater fundet

that contains the name to search for, we rst search upwards, among inherited attributes; if that fails then we take one step outwards from M and repeat.

When thinking of the boxes representing all the syntactically enclosing Main-Parts as a spine we may describe the global search as a series of local searches, each one starting from the next more globalMainPartin the spine. In the spe-cial case where there is no inheritance the global search is reduced to a simple, lexical scoping lookup mechanism.

All the mixins are contextually located inside their specic enclosing objects, so for instance the employee pattern which is looked up from our meeting context will be that employeepattern which is contextually located inside the

company where the meeting also (indirectly) belongs. So themeeting and the

employeeare a natural pair, as opposed to a combination of a meeting in one

companyand anemployeefrom another company.

It is exactly this kind of automatic, statically checked, multi-level object relation consistency support which is the core functionality of general block structureit allows us to safely and conveniently work with groups of objects and patterns that naturally belong together, and the grouping mechanism oers both great exibility by being nestable to any desired depth, and comprehen-sibility because of the deep and life-long experience that human beings have in exploiting contextuality.

As mentioned in the previous section, this kind of consistency support is also a restriction which may be too rigid in some casesand, as mentioned, there is always the option of using explicit associations, e.g., by manually maintaining an explicitmyProjectreference in eachmeetingobject. Such trade-os between convenient safety and more verbose exibility must be made all the time in the construction of programs; it is basically the same kind of trade-o as between a

whilestatement which allows only a very regular set of control ows, and agoto statement, which allows you to jump to any location in a program. History seems to support the assumption that the choice of a rigid but analyzable construct instead of a more powerful and exible construct often makes sense, perhaps because understandability is the more precious resource in the development of complex systems. Again, similar to the case withgotovs.while, since a at set of global classes is just a special case of general block structure, it is trivial to see that no expressive power is lost by having support for general block structure in a language.

5.4 The Relation to Modules

General block structure shares a few features with modularization, so it is useful to describe the dierences between them. If one of them turned out to be the more powerful mechanism, capable of solving all the tasks assigned to the other, then we had better use that one and get rid of the other mechanism altogether.

However, we think that general block structure and modularization are largely orthogonal mechanisms, hence it is actually useful to have both.

Similarities:

First a few reasons why they seem to overlap: If a given entity E (such as a class) is used as part of the solution S to a complex problem, but E does not play any rôle in the intended use of S (possibly available in a manifest form as a specication ofSor as an interface toS), then we can do two dierent things to reduce the overall system complexity: We may provide the functionality ofSas a module andE may be dened as private in that module, such that nobody needs to worry about whatE is or how it is used except for those who implement or maintain that module. Alternatively, we may deneE in context of a class that implements a solution toS; we assume that this class is global, to make the alternatives as comparable as possible.

In both cases, E is removed from the global name space, so programmers will not be bothered withE unless they take a look insideS. Similarly, when somebody does need to look at E, it will be clear already from the location of E in the source code thatE is supposed to be understood in context ofS. Finally, with multiple entities similar toS,S1:::Sn, and many entities similar to E, it will be convenient to compose large, complex systems using a subset ofS1:::Sn, because eachSi along with theE-like entities needed bySi can be provided as a named package (module or class) which can be manipulated as a whole; that amounts to better support for reuse of theSi's. One thing we have left out of this picture is the need for shared resourcesmodules will need an import mechanism in order to be useful, and with the block structure approach there would generally be dependencies between theSiclasses such that the use of one of them would imply the use of a number of others. However, that can be taken into account without changing the conclusions above.

It seems that both mechanisms support complexity reduction and reusability in large systems by grouping and containment of entities that do not have to be available for most of the system, thus allowing the remaining, generally useful entities to stand out all the more clearly. However, that description is deceptive for several reasons.

Dissimilarities:

Firstly, the nesting of an entity inside another does not make it inaccessible for outside entities. As an example of why it would be a bad idea to introduce restrictions that would make nested entities inaccessible from the outside, consider the company example from the previous section. Nested entities like project and meeting are perfectly valid concepts in connection with companies, they are not just implementation details that should be hidden from public view. In some languages, e.g. Smalltalk, there is a rule saying that methods are public, but instance variables are private. This ensures that access from the outside will always be mediated by a computation, as opposed to a simple variable access, and that again ensures that the implementation can be changed more freely without aecting code that uses the class. Now, this argument only makes sense if the access to a method and to an instance variable from the outside look dierent, otherwise the instance variable could simply be changed to one or two methods if needed, and all usage points would remain unchanged. Self, CLOS, Dylan, Cecil, and other languages transform access to

5.4. THE RELATION TO MODULES 117 variables (data slots) into method invocations by means of accessor methods, andBetaandgbetasupport a slightly dierent kind of transparency by means of coercions. In any case, the distinction between public computation and private state seems to be an artifact of a too meager transparency support. Other languages, including Simula, Java, C++, and Eiel, use a separate mechanism for access control management, namely explicit declarations of attributes as being

`private', `public', or accessible from specic entities (e.g. export declarations in Eiel and `friend' classes or functions in C++). Since this in all cases amounts to an orthogonal mechanism, independent of the block structure, it actually supports the claim that block structure and privacy management are separate issues.

Secondly, the tasks of name space partitioning and visibility management which are associated with module systems are static in nature. They are con-cerned with proporties of source code, not with properties of run-time entities.

In contrast, general block structure is inseparable from run-time entities, it is concerned with the ability of each nested run-time entity to depend on all its enclosing entities. By enabling this it also supports the grouping of run-time entities nested at some level under a common enclosing entity, for instance the grouping of meetings and employees together if and only if they belong to the same company. From the point of view of a module system there would just bemeetings andemployees, and an individual relation between a specic

meeting and a specic employee can not be expressed. Conversely, general block structure does not support the separation of individually reusable enti-ties, because even global entities are nested inside some outermost universe entity. The ability to compose a system from several smaller units is essentially a module-related capabilityand the above claim that block structure could be used to support separately reusable packages silently assumed that such sepa-rate packages could even be expressed and composed; that would require some form of module system, thereby invalidating the argument that general block structure could support the reuse on its own.

Finally, modules serve well as a means for physical organization of code, for example to separate interface and implementation, or to allow for the combina-tion of a given interface with any of several possible implementacombina-tions, such as one for each of a number of dierent hardware and/or software platforms. This again enables separate compilation, and it allows for ne-grained source code control [74, Ch. 17]. General block structure does not support the grouping of source code entities according to such concerns as separation of interface and implementation. For example, an entity in an enclosing scope may be an im-plementation detail that a given nested entity does not need to depend one.g.

if the company had some attribute which was used in the implementation of

companyitself but not needed byprojectormeeting. As another example, an entity may not need or use the context, but it may still be an implementation detail for a nested entitye.g. if a specialized data structure were used in the implementation of meeting, but the data structure did not depend oncompany orproject. Sometimes it is impossible to use the block structure to hide things appropriately, sometimes it is just wrong, because it introduces useless

contex-tual dependencies. Since contexcontex-tuality allows us to make entities that may be considered as a group more comprehensible, it is confusing and damaging for the usability of an entity if it is nested inside another entity and the enclosing and nested entities make no sense as a group. Hence, block structure should not be used for physical organization of code.

So, to summarize, modules are used to control visibility and/or accessibility for static entities, i.e., for pieces of source code; and for packaging related pieces of source code into conveniently reusable units; and for separating dierent kinds of pieces of source code independently of the semantic properties such as nesting location, e.g. for separating interface from implementation. General block structure is used to support contextual dependencies between run-time entities. Neither mechanism is able to handle the tasks assigned to the other.

Other points of view:

Earlier treatments of related topics do not consider contextuality, which is the main point in our argument for keeping classes and modules separate, but otherwise the argumentation is similar.

In the classic paper [91], modularization of programs is for the rst time introduced as a concept and a concern in its own right, and the main criterion given for modularization is that each module should encapsulate a design de-cision by providing its services to other modules in a form which is useful for the solution of the problem at hand, yet does not have to change if and when another choice is taken with respect to that design decision. Today the phrase

`representation' or `implementation' seems to cover the term `design decision' as it is used in [91]. The claimed results are that the system as a whole tolerates many changes inside modules without forcing changes to other modules, the system can be developed in parallel as soon as the interfaces have been chosen, and the system as a whole becomes easier to understand. The results in this paper are so well established today that they seem obvious.

In the Eiel community, the position is that classes and modules should be unied, such that there is only one structuring construct in the language [79].

This unication is made into a principle, required for `pure' object-orientation, and the criterion is the same as the one we gave above: If one mechanism can handle all the tasks of another mechanism, then the rst one should be used and the second one abandoned. However, the module and class mechanisms can only be unied in Eiel because there is no support for contextuality except for the nesting of method invocations inside objects. In particular this means that classes can be entirely static entities, and they are all naturally located in one, global name space. We just need to require that each module must consist of exactly one class denition, and then classes and modules are unied!

This does have some confusing consequences, though. For instance, there are standard Eiel classes such as MATH and BASIC_IO containing sets of procedures and functions for doing trigonometric computations and for receiv-ing keyboard input and writreceiv-ing text to a console. It is necessary to inherit from MATH in order to compute the cosine of an angle, and it is necessary to inherit from BASIC_IO in order to receive keyboard input; this inheritance relation

5.4. THE RELATION TO MODULES 119 does not make sense as a specialization, and the MATH and BASIC_IO classes themselves do not make sense as generators of contexts for the procedures in-side them. On the other hand, it makes good sense to conin-sider MATH and BASIC_IO as importable modules containing global procedures and functions.

Since we are generally in favor of unication of concepts, it is worth con-sidering whether a both-module-and-class concept is a good idea. There are some serious problems, however. Firstly, it is only possible when classes are static entities, so it cannot be applied to languages with general block struc-ture. Secondly, there is no support for physical separation of the interface and the implementation of a class, so even strictly implementation related changes to a class will cause recompilations, new versions of les, etc. It may be possible to compensate somewhat for these problems by using a smart compiler and linker and version control system etc., but it seems unnecessary to introduce those problems in the rst place. Finally, the functional granularity of the sys-tem may not be at the class levelif a group of classes is only meaningful taken together (say, NODE and EDGE which can be used together to create graphs), then it seems counter-productive to require that this group must be handled as a multitude of separate modules.

In [106], the need for modules as a separate construct in addition to classes is treated in detail, and the main reasons given in favor of having both classes and modules in a language are as follows: the import and the inheritance re-lation should not be confused; groups of classes may need to collaborate in order to maintain invariants; selective export (as in Eiel or asfriendin C++) cause hard-to-understand networks of visibility; and modules allow both sep-arate compilation per module and gives good opportunities for optimizations inside a module, since many optimizations are concerned with interactions be-tween tightly cooperating classes. Apart from the fact that this does not cover contextuality, we support this argumentation, and again the conclusion is that it is appropriate to have both classes and modules.

Propagation of Specialization

This chapter shares material with our paper Propagating Class and Method Combination, which was accepted for publication and presen-tation at the ECOOP'99 conference.

In recent years the management of concerns involving multiple classes and the combination of structure and behavior from separate entities has been a very active area of research. Subject orientation [52], aspect oriented program-ming [57], and object collaborations [81] are all examples of such eorts. The support for general pattern merging and the semantics of virtuals in gbeta pro-vides a language integrated approach to the achievement of these goals. A seam-less integration into a statically typed general purpose programming language such asgbeta opens the possibility for type checking at the level of the multi-class constructs, separate type-checking and compilation, and avoidance of the impedance problems associated with the use of several dierent mind-sets, languages, and tools.

The general block structure enables a natural expression of groups of mutu-ally dependent patterns. The very exible inheritance and pattern combination mechanism interacts with the block structure to support propagation of pattern combinations. The reason why we use the word `propagation' to describe this phenomenon is that it allows programmers to initiate a complex but regular pro-cess by specifying a syntactically simple pattern merging operation, for example by an expression likea & b, and as a result of the semantics of virtual attributes (see Chap. 4 for details), this combination of aandbcan propagate to cause the combination of some virtual attributes inaandb, and possibly also propagate further into virtual attributes nested inside those virtuals etc., and nally it propagates the enrichment of all those virtuals into all the patterns that inherit from them. In other words, one syntactically explicit combination operation may cause many other combination and specialization operations on dependent patterns, where the dependency relations are either `is-a-virtual-attribute-of' or

`inherits-from'.

This description of propagation as something that moves along the edges of a graph of dependency links (some caused by simple syntactic nesting, some

121

established in static analysis) illuminates how similar it is to a constraint solving process. Constraints are introduced by declarations of virtual attributeswhere the constraint is on the formaband by inheritancewhere the constraint is often on the form a = b&[(# ::: #)]. Other forms of constraints are also available, for example lower bounds on virtuals, which are presented in Sect. 9.2.

Note that this constraint solving process may happen at run-time or at compile-time. There is full support for performing the process at run-time, as described in Chap. 7, but a warning will be issued for each location in a program where this constraint solving process cannot be analyzed fully at compile-time;

that is the case, for instance, when two variable patterns are merged.

This chapter gives a survey of signicant usages of the propagating combina-tion mechanism, thus illustrating the semantics and motivating its usefulness.

6.1 Combination of Classes, then Methods

The rst example illustrates the use of propagation in only one level; this special case works similarly to CLOS method combination using before and after methods, thus illustrating thegbetapattern combination mechanism by showing how it achieves a known goal. Explained in terms of propagation, this is about combination of two classes andby propagationcombination of the methods inside those classes.

Consider an abstract pattern Stackwhich species a stack data structure, along with a specializationStackImplwhich contributes an implementation of the stack using a list (whose type constraint on contained objects (element) is specied to be the same as the constraint given for the enclosingStackImpl):

Stack:

1(# element:< object;

1(# element:< object;