• No results found

2.3 Review

2.3.2 Contra

Programming today is a race between software engineers striving to build bigger and better idiot-proof programs, and the Universe trying to produce bigger and better idiots. So far, the Universe is winning. – Rich Cook 2.3.2.1 Imperative heritage

Most object-oriented languages are based on ALGOL-like languages (C++, SIMULA,

EIFFEL) and some on LISP (SMALLTALK, CLOS, DYLAN). All share an imperative

base trough their ancestors. While we argued in section 2.3.1.2 on page 39 that object-oriented languages reach beyond their imperative roots, they still suffer from their imperative heritage. One example are system shut-downs due to method calls on void references. This is clearly an initialization problem which in general has already been criticized by John Backus [Backus78] and which we will tackle in chapter 11 on page 191.

Aliasing Consider a program fragment using a complex number library:

!!a.make(2,0); b:=a;

c:=a+b;

!!a.make(0,2); d:=a+b;

We would expectdto have the value2+2ibut if complex numbers are implemented with reference semantics — which is quite likely to be the case due to efficiency considerations — the result will be4i. The reason for this is the unexpected aliasing ofbwitha. Though object-oriented languages typically provide support for value semantics (e.g., expanded classes in EIFFEL, part-objects in BETA, non-reference

variables in C++) there is no agreed system how to choose between reference and value semantics.

Another source of aliasing are interferences between parameters and results. A method does not represent a good abstraction if the effects of

o.m(x, y)

differ to

o.m(x, x).

Also,

aMatrix.multiply(aMatrix2)

possibly yields the wrong result whenaMatrix2 happens to referenceaMatrix, since then result elements will override still to be used argument elements.

Broken encapsulation When object state is distributed over several sub-objects and these objects are accessible from outside the object the state-encapsulation of the object is broken. For instance, a careful bank customer, aiming at never having less than $2000 on his account, will be invalidated by his wife, if she has access to the account too [Duke95]. Similarly, a balanced tree can get unbalanced if one of its element is changed by a side-effect. A solution for these problems has been proposed by the use of so-called Islands [Hogg91].

2.3.2.2 Classes for everything

The class concept is used for representing modules, encapsulation borders, in- stance generation, code pools, types, visibility mechanism, and interfaces. Al- though the strive for unification, i.e., to capture many things with a single ab- straction, is desirable, the wealth of a class’ functions appears too much. There has been arguments to separate modules and classes [Szyperski92], code pools and types [Murer et al.93a], and encapsulation and classes [Limberghen & Mens94]. So- called Kinds have been proposed to aid classes with typing [Yu & Zhuang95] and “namespaces” are supposed to cluster C++ classes. Obviously, there will be more research necessary to remove the overloading of concepts on classes.

2.3.2.3 Inheritance for everything

A modeling tool is more than a passive medium for recording our view of reality. It shapes that view, and limits our perceptions. If a mind is committed to a certain [tool], then it will perform amazing feats of distortion to see things structured that way, and it will simply be blind to the things which don’t fit that structure [Kent78]. – W. Kent

Inheritance has been the “hammer” for many problems (be they “nails” or not) for both language designers and language users. Though inheritance is primarily an incremental modification operator on classes it fulfills many purposes:

Language designers use inheritance for

Subtyping An heir is implicitly assumed to be a subtype to its ancestor and, thus, is allowed to be substituted for it.

Code reuse Code reuse happens through inheriting methods which continue to work in the heir but also by deliberately importing methods only meant to be used for the implementation of the heir.

Interfaces So-called pure, abstract, or deferred classes only specify interfaces and do not contain any code. They are used to define an interface that implemen- tations may adhere to.

Modules The collection of mathematical constants in the EIFFEL library is an ex- ample for solely using a class as a namespace and container of data without intending to use it as a instance generator.

Mixins Languages with multiple inheritance suggest a LEGOapproach to the com-

position of code. Each superclass mixin provides a facet of a class like per- sistence, observeability, etc. This style is also referred to as facility inheri-

tance [Meyer94b].

Language users exploit inheritance for

Classification Libraries are sometimes organized as a taxonomy of concepts (see figure 2.1 on page 30). This helps to understand classes in terms of others. Also, algorithms may be subject to classification [Schmitz92]. Each branch in the inheritance hierarchy then represents a design decision.

Specialization Heirs narrow the scope of ancestors, e.g., aStudentis a specialPer- son, typically by adding information (e.g., student identification). Special- ization may conform to subtyping (like in the SportsCarandCar example in figure 2.2 on page 31) but does not necessarily (a 3DPointis not a subtype of

Pointdue to the covariant equality method).

Generalization Heirs cancel limitations of their ancestors. For instance an all pur- pose vehicle may be subclassed to obtain an all purpose vehicle that allows steering of the rear axle. This usage is typically due to unforeseen extensions and coincides with code reuse.

Versioning As explained earlier (see figure 2.3 on page 33) it is possible to use heirs as versions of their ancestors. This allows extending data records in a system while reusing all of the control code unchanged.

Parameterization Partially abstract classes are used as ancestors to concrete sub- classes that specify the missing code. This type of code parameterization has been described as the Template Method pattern [Gamma et al.94].

The problem with all these uses for inheritance is that always the same rules for visibility, substitutability, and namespaces are applied. While breaking en- capsulation is necessary for white-box reuse it leads to fragile base classes in gen- eral [Snyder86, K ¨uhne95b]. Also,

inheritance used for code reuse or covariant specialization should not enable substitutability [LaLonde & Pugh91].

Inheritance used for parameterization should not break encapsula- tion [K ¨uhne95b].

Interface inheritance should not allow covariant redefinitions of argu- ments [Cook89b], etc.

Consult section 2.3.2.2 on page 42 for a list of suggestions to escape the “one-trick- pony” mentality towards inheritance in object-oriented languages.

Especially multiple inheritance causes additional problems like aggravating en- capsulation, modularity and the open-closed principle [Carr´e & Geib90], which is why many languages do not provide it (e.g., SMALLTALK, BETA, JAVA).

It can be argued about whether it is positive or negative that inheritance typ- ically is a static mechanism. There is no doubt, however, that inheritance should not be used when more dynamic solutions are appropriate. We will return to this subject in chapter 7 on page 93.

3

Calculus comparison

A

s we are going to subsume functional concepts with the object-oriented paradigm in chapter 4 on page 55 and aim at capturing functional tech- niques with object-oriented design patterns in part II starting at page 85 this chapter shall assure us about the validity of this approach.

Of course, all functional and object-oriented languages are Turing complete with regard to their computation power. There is no doubt that any of these lan- guages can emulate one of the other. In spite of that it is quite interesting to ask whether one emulation is easier to accomplish than the other. If it is easier to cap- ture object-oriented programming with a functional language we should write pat- terns facilitating object-oriented programming in a functional language. The goal of this chapter is to prove the opposite.

3.1 Language comparisons

Looking for the computational difference between two languages is like comparing apples to oranges only to find out that both are fruit. – me

What is left if we beware of falling into to the Turing tarpit, i.e., try to find a difference in computational power that is simply not there? What other indica- tors for expressiveness are available? One interesting approach is to evaluate the amount of restructuring a program necessary to emulate the addition of new con- struct [Felleisen91]. If local changes are necessary only then the new construct can be judged to add no further expressiveness. Unfortunately, the theoretical frame- work used for this approach does work for conservative extensions to languages only, i.e., cannot be used to compare two fundamentally different languages.

For the purpose of this chapter my idea was to define a translation of one lan- guage into the other and vice versa and then compare the translation complexities. The rational is that a language should emulate a less expressive language with ease while a considerable amount of machinery would be necessary the other way round.

In order to compare the fundamental mechanisms instead of technicalities of specific languages it appeared most reasonable to compare calculi expressing the nature of the two paradigms respectively. The calculus representing functional pro- gramming is easily found and is referred to as the λ-calculus. You might want to

refer to an introduction for its syntax and semantics [Barendregt84, Hankin94]. It is much less obvious to choose a calculus representing the object-oriented paradigm. Many approaches to model object-oriented principles with calculi use an extended version of the λ-calculus or do contain first-order functions [Abadi94, Bruce94, Cardelli84, Cook89a, Fisher & Mitchel95, Pierce & Turner94, Reddy88]. A com- parison with such a calculus would be ridiculous because of the trivial emula- tion of the λ-calculus within it. When I made the decision which calculus to choose there where two candidates that did not model objects as records in a functional language but attempted to model object-oriented languages from first principles. One was OPUS [Mens et al.94, Mens et al.95] and the other was the σ-

calculus [Abadi & Cardelli94]. In fact, the latter calculus was inappropriate too for it allowed to bind the self reference of an object to an arbitrary name and, thus, enabled to access variables of arbitrary nesting depth. In a comparison carried out by Dirk Thierbach this feature was found out to be very “functional” in na- ture [Thierbach96] and also would have prevented a fair comparison. Thierbach also identified several weaknesses of OPUSand developed an improvement called

OPULUS. He proved OPULUS to have the Church-Rosser property and gave a com- parison to both OPUS andσ-calculus [Thierbach96].

Related documents