For instance, the class COMPARABLE is from the Eiffel library (the result shown is the result of extracting the flat-short version of the class).

deferred class interface COMPARABLE feature specification infix "<" (other: like Current): BOOLEAN is deferred; infix "<=" (other: like Current): BOOLEAN is deferred; infix ">" (other: like Current): BOOLEAN is deferred; infix ">=" (other: like Current): BOOLEAN is deferred; end interface -- class COMPARABLE

Now define

class INTORD feature value:INTEGER; infix "<"(other:like Current) is do Result := value < other.value end; ... end -- class INTORD

Can use in

class Sorting[T -> COMPARABLE] feature sort(thearray:ARRAY[T]):ARRAY[T] is local .... do ...... .... if thearray.item(i) < thearray.item(j) .... end;

Subclasses can see all features, whether exported or not.

Eiffel has tools to

- extract specification from class: "short"
- Combine all features available in class (whether defined locally or inherited): "flatten"

**Allowable changes which can be made in subclasses**:

- Can add new features (instance vbles or routines).
- Instance variables may be given a new type which is a subclass of the
original.
- In redefining routines, may replace parameter and result types by types which are subclasses of originals. (Note that this may be done automatically in inherited routines if type defined in terms of "like Current" or similar. See "same, lessthan" in NEWRATIONALS class.)

More flexible than Object Pascal or C++ (but leads to problems, below!)

Big problem with Eiffel - identification of class with type.

Say C' is a subclass (or heir) of C if C' inherits from C.

Thus C' inherits attributes and methods from superclass.

When redefine methods in subclass may replace class of arguments and answer by subclasses.

E.g.

If m(a:A):B in C then can redefine m(a:A'):B' in subclass C' if A' inherits from A and B' inherits from B.

Unfortunately, this can lead to holes in typing system.

Recall A' is a subtype of A if an element of type A' can be used in any context expecting an element of type A.

Eiffel allows programmer to use an element of subclass anywhere it expects an element of its superclass.

Therefore distinction between static and dynamic class!

Unfortunately subtype != subclass.

The following are slightly simplified examples from the Eiffel structure library. They represent singly and doubly-linked nodes.

class LINKABLE [G] feature item: G; right: like Current; -- Right neighbor put_right (other: like Current) is -- Put `other' to the right of current cell. do right := other ensure chained: right = other end; end -- class LINKABLENow define subclass:

class BI_LINKABLE [G] inherit LINKABLE [G] redefine put_right end feature -- Access left: like Current; -- Left neighbor put_right (other: like Current) is -- Put `other' to the right of current cell. do right := other; if (other /= Void) then other.simple_put_left (Current) end end; put_left (other: like Current) is -- Put `other' to the left of current cell. do left := other; if (other /= Void) then other.simple_put_right (Current) end ensure chained: left = other end; simple_put_right (other: like Current) is -- set `right' to `other' do if right /= Void then right.simple_forget_left; end; right := other end; simple_put_left (other: like Current) is -- set `left' to `other' is do if left /= Void then left.simple_forget_right end; left := other end; invariant right_symmetry: (right /= Void) implies (right.left = Current); left_symmetry: (left /= Void) implies (left.right = Current) end -- class BI_LINKABLE

So far so good.

But now suppose have following routine

trouble(p, q : LINKABLE [RATIONAL] ) is do p.put_right(q); .... end

and suppose have `s_node : LINKABLE [RATIONAL] `and `bi_node:
BI_LINKABLE [RATIONAL]`.

What happens if write:

trouble(bi_node,s_node)If

Problem is that

`s_node.put_right` takes a parameter of type (class) `LINKABLE
[RATIONAL] `

while `bi_node.put_right` takes a parameter of type (class):

BI_LINKABLE [RATIONAL]and these are not subtypes:

A' -> B' subtype of A -> B iff B' subtype of B and __A subtype of
A'__

*note reversal!*

With procedure can think of the return type as VOID.

Thus subclass in Eiffel does not always give legal subtype.

Hence get holes in type system.

Can also export method from superclass, but not from subclass. This will also break system if send message to object of subclass which is not visible.

E.g., define

hide_n_break(a:A) is do a.meth .... end

and then write `hide_n_break(a')` where `a' : A'`, and `A'
`is subclass of `A` which does not export `meth`.

Earlier versions of Eiffel allowed user to break the type system in these ways.

Eiffel 3.0 attempts to compensate by mandating a global check of all classes used in a system to make sure that above situation could not occur (class-level check and system-level check). One consequence is that a system could work fine,but addition of new (separately compiled) class could break a previously defined class.

Unfortunately no Eiffel compilers implement this system validity check.

In Fall, '95, Bertrand Meyer announced solutions to "covariant typing problem" at OOPSLA '95. Two days later I found a hole in the solution. It's been fixed, but other problems may remain.

Virtually all object-oriented language either provide holes like this or are so rigid they force the programmers to bypass the type system. For example, C++ doesn't allow user to change type of parameters of methods (new versions allow change in type of results of function methods), but has many, many, more holes, e.g., unchecked casts. Java inserts dynamic checks of casts to avoid type holes (but blew suptyping of array types - though add dynamic check).

Most statically typed object-oriented languages are either

- Type unsafe like Eiffel
- Too rigid with types (like C++ or Object Pascal) forcing programmers to bypass the type system. For example, C++ allows doesn't allow user to change type of methods (new versions allow change in type of results of function methods), but has many, many, more holes, e.g., unchecked casts.
- Insert dynamic checks where unsafe (Java, Beta).

Trellis/Owl (by DEC) avoids the problem by only allowing subclasses which are also subtypes, but this is pretty restrictive - rules out above COLORPOINT class.

Claim proper solution is to separate subtype and inheritance hierarchies (originally proposed by researchers in ABEL group at HP Labs and independently by P. America at Philips Research Labs)

Inheritance hierarchy has only to do with implementation.

Subtype hierarchy has only to do with interface.

Therefore class != type.

__Bonus__: Can have multiple classes generating objects of same
type

E.g., cartesian and polar points with same external interface.

Even though don't necessarily care if subclasses turn out to be subtypes, still need restrictions on redefinitions to avoid breaking other inherited methods.

Ex.:

method1(...) = ... p.method2(..).... method2(...) = .....

If now redefine method2 with different type, how do we know it will continue to be type-safe when used in method1 (presuming method1 is inherited and not changed).

One can set up type-checking rules for determining legal subclasses and subtypes and be guaranteed that can't break the typing system.

This is extremely important, since one of goals of object-oriented programming languages is to provide reusable libraries of components, much like that found with FORTRAN for numerical routines or Modula-2 for data structures.

Major advantage would be ability to make minor modifications to allow user to customize classes.

Sale of libraries is expected to become a major software industry. However, if selling library will typically only sell compiled version, not source code (though provide something like definition module).

If user doesn't see source code of superclass, how can s/he be confident that will get no type errors. Need the kind of guarantees claimed above.

Work here on TOOPLE, TOIL, PolyTOIL, and LOOM (involving honors theses by R. van Gent '93, A. Schuett '94, and L. Petersen '96, and supporting work by J. Rosenberg & S. Calvo '96) resulted in object-oriented language which is type-safe and only requires classes and methods to be type-checked once (don't have to repeat when inherit methods).

__Other Eiffel examples: __

__ PARENTHESES - simple example using STACK class from structures
library.__

**Evaluation of OOL's.**

__Pro's (at least of Eiffel)__

- Good use of information hiding. Objects can hide their state.
- Good support for reusability. Supports generics like Ada, run-time creation
of objects (unlike Ada)
- Support for inheritance and subtyping provides for reusability of code.

__Con's__

- Loss of locality. May have to look through many classes in order to
find meanings of methods of current class. Changes in superclasses may have
big impact on descendents.
- Type-checking too rigid, unsafe, or requires link time global analysis.
Class that has worked in the past may break when linked with new customer
class. Avoidable if either greatly restrict inheritance or separate subtyping
and inheritance hierarchies. Some languages insert run-time checks to cover
lack of type-safety.
- Semantics of inheritance is very complex. Small changes in methods may make major changes in semantics of subclass. It appears you must know definition of methods in superclass in order to predict impact on changes in subclass. Makes provision of libraries more complex.

Eiffel also provides support for number of features of modern software engineering - e.g., assertions.

Could be a very important language if fixed type problems - Sather is one attempt.

What will be impact of OOL's on programmers and computer science?

Large number of powerful players jumped on the bandwagon without careful assessment of consequences. Now growing reaction against C++.

Many OO programmers don't really understand paradigm, esp. if use OO add-on to older language.

Suspect that most of the advantages claimed by proponents could be realized in Clu, Modula-2, or Ada (all available decade or more ago).

Some languages (Modula-3, Haskell, Quest, etc.) provide subtyping without inheritance. Seem to be few problems associated with this.

My advice: specify carefully meaning of methods, avoid long inheritance chains, and be careful of interactions of methods. If implement generics, Java could be a very successful compromise between flexibility and usefulness.

- Operational
- Axiomatic
- Denotational

May have originated with idea that definition of language be an actual implementation. E.g. FORTRAN on IBM 704.

Can be too dependent on features of actual hardware. Hard to tell if other implementations define same language.

Now define abstract machine, and give translation of language onto abstract machine. Need only interpret that abstract machine on actual machine to get implementation.

Ex: Interpreters for PCF. Transformed a program into a "normal form" program (can't be further reduced). More complex with language with states.

Expressions reduce to pair (v,s), Commands reduce to new state, s.

E.g.

(e1,rho, s) => (m, s') (e2,rho, s') => (n, s'') ---------------------------------------------------- (e1 + e2,rho, s) => (m+n, s'') (M,rho, s') => (v, s'') ---------------------------------------- (X := M,rho, s) => (rho, s''[v/rho(X)]) (fun(X).M,rho, s) => (< fun(X).M,rho>, s) (f,rho,s) => (<fun(X).M,rho'>, s') (N,rho,s') => (v,s''), (M,rho' [v/X], s'') => (v', s''' ) ------------------------------------------------------------ (f(N),rho, s) => (v', s''' )

Meaning of program is sequence of states that machine goes through in executing it - trace of execution. Essentially an interpreter for language.

Very useful for compiler writers since very low-level description.

Idea is abstract machine is simple enough that it is impossible to misunderstand its operation.

No model of execution.

Definition tells what may be proved about programs. Associate axiom with each construct of language. Rules for composing pieces into more complex programs.

Meaning of construct is given in terms of assertions about computation state before and after execution.

General form:

{P} statement {Q}where P and Q are assertions.

Meaning is that if P is true before execution of statement and statement terminates, then Q must be true after termination.

Assignment axiom:

{P [expression / id]} id := expression {P}

e.g.

{a+17 > 0} x := a+17 {x > 0}or

{x > 1} x := x - 1 {x > 0}

__While rule:__

__
__

If {P & B} stats {P}, then {P} while B do stats {P & not B}

E.g. if P is an invariant of stats, then after execution of the loop, P will still be true but B will have failed.

Composition:

If {P} S1 {Q}, {R} S2 {T}, and Q => R, then {P} S1; S2 {T}

Conditional:

If {P & B} S1 {Q}, {P & not B} S2 {Q}, then {P} if B then S1 else S2 {Q}

Consequence:

If P => Q, R => T, and {Q} S {R}, then {P} S {T}

Prove program correct if show

{Precondition} Prog {PostCondition}

Often easiest to work backwards from Postcondition to Precondition.

Ex:

{Precondition: exponent0 >= 0} base <- base0 exponent <- exponent0 ans <- 1 while exponent > 0 do {assert: ans * (base ** exponent) = base0 ** exponent0} { & exponent >= 0} if odd(exponent) then ans<- ans*base exponent <- exponent - 1 else base <- base * base exponent <- exponent div 2 end if end while {Postcondition: exponent = 0} { & ans = base0 ** exponent0}

__Let us show that:__

P = ans * (base ** exponent) = (base0 ** exponent0) & exponent >= 0is an invariant assertion of the while loop.

The proof rule for a while loop is:

If {P & B} S {P} then {P} While B do S {P & not-B}We need to show P above is invariant (i.e., verify that

Thus we must show:

{P & exponent > 0} if odd(exponent) then ans<- ans*base exponent <- exponent - 1 else base <- base * base exponent <- exponent div 2 end if {P}However, the if..then..else.. rule is:

if {P & B} S1 {Q} and {P & not-B} S2 {Q} then {P} if B then S1 else S2 {Q}.Thus it will be sufficient if we can show

(1) {P & exponent > 0 & odd(exponent)} ans<- ans*base; exponent <- exponent - 1 {P}and

(2) {P & exponent > 0 & not-odd(exponent)} base <- base * base; exponent <- exponent div 2 {P}But these are now relatively straight-forward to show. We do (1) in detail and leave (2) as an exercise.

Recall the assignment axiom is `{P[exp/X]} X := exp {P}`.

If we push P "back" through the two assignment statements in (1), we get:

{P[ans*base/ans][exponent - 1/exponent]} ans<- ans*base; exponent <- exponent - 1 {P}But if we make these substitutions in P we get the precondition is:

ans*base* (base ** (exponent - 1)) = base0 ** exponent0 & exponent - 1 >= 0which can be rewritten using rules of exponents as:

ans*(base ** exponent) = base0 ** exponent0 & exponent >= 1Thus, by the assignment axiom (applied twice) we get

(3) {ans*(base**exponent) = base0**exponent0 & exponent >= 1} base <- base * base; exponent <- exponent div 2 {P}

Because we have the rule:

If {R} S {Q} and R' => R then {R'} S {Q}To prove (1), all we have to do is show that

(3) P & exponent > 0 & odd(exponent) => ans*(base ** exponent) = base0 ** exponent0 & exponent >= 1where P is

ans*(base**exponent) = (base0**exponent0) & exponent >= 0.Since

However `exponent > 0 & odd(exponent) => exponent >= 1.`

Thus (3) is true and hence (1) is true.

A similar proof shows that (2) is true, and hence that P truly is an invariant of the while loop!

Axiomatic semantics due to Floyd & Hoare, Dijkstra also major contributor. Used to define semantics of Pascal [Hoare & Wirth, 1973]

Too high level to be of much use to compiler writers.

Perfect for proving programs correct.