Adding new observers
OOP
template< class E >
int length(list< E >* l) { length
return l->empty() ? 0 : 1 + length( l->tail() );
}
template< class E >
class listWL : public list<E> { listWL
public:
int length() { return ::length( this ); }
};
slide: Objects and observers
A program fragment illustrating the use of the
listWL class is given below.
list<int>* r = new cons<int>(1,new cons<int>(2,new interval(3,7)));
while (! r->empty()) {
cout << ((listWL< int >*)r)->length() << endl;
r = r->tail();
}
delete r;
Evidently, we need to
employ a cast whenever we wish to
apply the length observer function.
Hence, this seems not to be the right solution.
Alternatively, we may use the function length directly.
However, we are then forced to mix method
syntax of the form with function syntax
of the form , which may easily lead
to confusion.
Discussion
We may wonder why an object-oriented approach,
that is supposed to support extensibility, is
at a disadvantage here when compared to a more
traditional module-based approach.
As observed in [Cook90], the problem lies in the fact
that neither of the two approaches reflect
the full potential and flexibility of
the matrix specification of an abstract data type.
Each of the approaches represents a particular choice
with respect to the decomposition of the matrix, into
either an operations-oriented (horizontal) decomposition
or a data-oriented (vertical) decomposition.
The apparent misbehavior of an object realization
with respect to extending the specification with observer
functions explains why in some cases we prefer
the use of overloaded functions rather than methods,
since overloaded functions allow for implicit dispatching
to take place on multiple arguments,
whereas method dispatching behavior is determined only
by the type of the object.
However, it must be noted that the dispatching behavior
of overloaded functions in C++ is of a purely syntactic nature.
This means that we cannot exploit the information
specific for a class type as we can when using
virtual functions.
Hence, to employ this information we would be required
to write as many variants of overloaded functions
as there are combinations of argument
types.
Dynamic dispatching on multiple arguments is
supported by
multi-methods in CLOS,
see [Paepcke93].
According to [Cook90], the need for such methods
might be taken as a hint that objects
only partially realize the true potential of data abstraction.
object-oriented programming
Types versus classes
Types are primarily an aid in arriving at a consistent
system description.
Most (typed) object-oriented programming languages offer
support for types by employing classes as a device to define the
functionality of objects.
Classes, however, have originated from a far more pragmatic concern,
namely as a construct to enable the definition and creation of objects.
Concluding this chapter, we will reflect on the distinction between
types and classes, and discuss the role types and classes
play in reusing software through derivation by inheritance.
This discussion is meant to prepare the ground for a more formal
treatment to be given in the next chapter.
It closely follows the exposition given in [WZ88].
Types must primarily be understood as predicates to guide
the process of type checking,
whereas classes have come into being originally
as templates for object creation.
It is interesting to note how (and how easily) this
distinction may be obscured.
In practice, when compiling a program in Java or C++, the compiler will
notify the user of an error when a member function is called
that is not listed in the public interface of the objects class.
As another example, the runtime system of Smalltalk
will raise an exception, notifying the user of a dynamic type
error, when a method is invoked that is not defined in the object's
class or any of its superclasses.
Both kinds of errors have the flavor of a typing error,
yet they rely on different notions of typing and
are based on a radically different interpretation of classes as types.
To put types into perspective, we must ask ourselves what means we have
to indicate the type of an expression, including expressions
that somehow reference a class description.
In [WZ88], three attitudes towards typing are distinguished:
(1) typing may be regarded as an administrative aid to check
for simple typos and other administrative
errors, (2) typing may be regarded
as the ultimate solution to defining the behavior of a system,
or (3) typing may (pragmatically) be regarded as a consequence
of defining the behavior of an object.
See slide [8-classes].
Before continuing, the reader is invited to sort the various
programming languages discussed into the three slots mentioned.
Types versus classes
- types -- type checking predicates
- classes -- templates for object creation
Type specification
- syntactically -- signature
(under)
- semantically -- behavior
(right)
- pragmatically -- implementation
(over)
slide: Types and classes
Typing as an administrative aid is typically a task
for which we rely on a compiler to check
for possible errors.
Evidently, the notion of typing that a compiler employs is
of a rather syntactic nature.
Provided we have specified a signature correctly, we may trust
a compiler with the routine of checking for errors.
As languages that supports signature type checking we may (obviously)
mention Java and C++.
Evidently, we cannot trust the compiler to detect conceptual errors,
that is incomplete or ill-conceived definitions of the functionality of an object
or collections of objects.
Yet, ultimately we want to be able to specify the behavior of an
object in a formal way and to check mechanically for the adequacy
of this definition.
This ideal of semantic types underlies the design of Eiffel,
not so much the Eiffel type system as supported by the Eiffel
compiler, but the integration of assertions in the Eiffel language
and the notion of contracts as a design principle.
Pragmatically, we need to rely on runtime (consistency) checks
to detect erroneous behavior, since there are (theoretically rather severe)
limits on the extent to which we may verify behavioral properties
in advance.
(Nevertheless, see section [types-behavioral] for some attempts in
this direction.)
Modifications
- types -(predicate constraints) subtypes
- classes -(template modification) subclasses
Varieties of (compatible) modifications
- behaviorally -- algebraic, axiomatic
(type)
- signature -- type checking
(signature)
- name -- method search algorithm
(classes)
slide: Type modifications
Finally, we can take a far more pragmatic view towards
typing, by regarding the actual specification of a class as an implicit
characterization of the type of the instances of the class.
Actually, this is the way (not surprisingly, I would say)
types are dealt with in Smalltalk.
Each object in Smalltalk is typed, by virtue of being
an instance of a class.
Yet, a typing error may only be detected dynamically, as the result of
not responding to a message.
A distinction between perspectives on types
(respectively syntactic, behavioral and pragmatic)
may seem rather academic at first sight.
However, the differences are, so to speak, amplified when
studied in the context of type modifications,
as for example effected by inheritance.
[WZ88] make a distinction between three notions of
compatible modifications, corresponding to the three
perspectives on types, respectively
signature compatible modifications
(which require the preservation of the static signature),
behaviorally compatible modification
(which rely on a mathematical notion of definability for a type)
and name compatible modifications
(that rely on an operationally defined method search algorithm).
See slide [8-refinement].
Signature compatible modifications
The assumption underlying the notion of types as signatures
is that behavior is approximated by a (static) signature.
Now the question is: to what extent can we define semantics preserving
extensions to a given class or object?
Signature compatible modifications
- behavior is approximated by signature
Semantics preserving extensions
- horizontal -- Person = Citizen + { age : 0..120 }
- vertical -- Retiree = Person + { age : 65..120 }
Principle of substitutability
- an instance of a subtype can always be used in any context in which an instance of a supertype can be used
subsets are not subtypes
Retiree Person
Read-only substitutability
- subset subtypes, isomorphically embedded subtypes
slide: The principle of substitutability
When we conceive of an object as a record consisting of
(data and method) fields, we may think of two possible kinds of modifications.
We may think of a horizontal modification when adding a new field,
and similarly we may think of a modification as being vertical
when redefining or constraining a particular field.
For example, when we define Citizen as an entity with a name,
we may define (at the risk of being somewhat awkward)
a Person as a Citizen with an age and a Retiree as a Person
with an age that is restricted to the range 65..120.
The principle by which we may judge these extensions valid (or not)
may be characterized as the principle of substitutability,
which may be phrased as:
an instance of a subtype can always be used in any context in
which an instance of a supertype can be used.
Unfortunately, for the extension given here we have an easy
counterexample, showing that syntactic signature compatibility
is not sufficient.
Clearly, a Person is a supertype of Retiree
(we will demonstrate this more precisely in section [subtypes]).
Assume that we have a function
set_age : Person * Integer -> Void
that is defined as set_age(p,n) { p.age = n; }.
Now consider the following fragment of code:
Person* p = r; r refers to some Retiree
p->set_age(40);
where we employ object reference notation when calling .
Since we have assigned r (which is referring to a Retiree)
to p, we know that p now points to a Retiree,
and since a Retiree is a person we may apply
the function .
However, sets the age of the Retiree to 40,
which gives (by common standards) a semantic error.
The lesson that we may draw from this is that being
a subset is no guarantee for being a subtype
as defined by the principle of substitutability.
However, we may characterize the relation between
a Retiree and a Person as being of a weaker kind,
namely read-only substitutability,
expressing that the (value of) the subtype may be used safely
everywhere an instance of the supertype is expected,
as long as it is not modified.
Read-only substitutability holds for a type that stands
in a subset relation to another type
or is embeddable (as a subset) into that type.
See slide [8-subst].
Behaviorally compatible modifications
If the subset relation is not a sufficient condition for
being in a subtype relation, what is?
To establish whether the (stronger) substitutability relation holds
we must take the possible functions associated with the types
into consideration as well.
First, let us consider what relations may exist between types.
Recall that semantically a type corresponds to
a set together with a collection of operations
that are defined for the set
and that the subtype relation corresponds to
the subset relation in the sense that (taking a type as a constraint)
the definition of a subtype involves adding a constraint and,
consequently,
a narrowing of the set of elements corresponding to the supertype.
Complete compatibility is what we achieve when the principle
of substitutability holds.
Theoretically, complete compatibility may be assured when the behavior of the
subtype fully complies with the behavior of the supertype.
Behavioral compatibility, however, is a quite demanding notion.
We will deal with it more extensively in chapter [refinement],
when discussing behavioral refinement.
Unfortunately, in practice we must often rely
on
the theoretically much weaker notion of
name compatibility.
Name compatible modifications
- operational semantics -- no extra compile/run-time checks
procedure search(name, module)
if name = action then do action
elsif inherited = nil
then undefined
else search(name, inherited)
slide: The inheritance search algorithm
Name compatible modifications
Name compatible modifications approximate behaviorally
compatible modifications in the sense that
substitutability is guaranteed, albeit not in a semantically
verifiable way.
Operationally, substitutability can be enforced
by requiring that each subclass (that we may characterize
as a pragmatic subtype) provides at least the operations
of its superclasses
(while giving a sensible result on all argument types
allowed by its superclasses).
Actually, name compatibility is an immediate consequence of
the overriding semantics of derivation by
inheritance, as reflected in the search algorithm underlying
method lookup.
See slide [8-search].
Although name compatible modifications are by far the most flexible,
from a theoretical point of view they are the least satisfying
since they do not allow for any theory formation concerning
the (desired) behavior of (the components of) the system
under development.
object-oriented programming
Summary
object-oriented programming
This chapter has presented an introduction
to the theoretical foundations of
abstract data types.
In particular, a characterization was
given of types as constraints.
1
- abstraction -- control and data
- abstract data types -- values in a semantic domain
- types as constraints -- mathematical models
slide: Section 8.1: Abstraction and types
In section 1,
we discussed the notion of abstraction
in programming languages and
distinguished between control and
data abstractions.
Abstract data types were characterized as values in some domain,
and we looked at the various ways in which
to define mathematical models
for types.
2
- signature -- producers and observers
- generator universe -- equivalence classes
- initial model -- no junk, no confusion
- objects -- multiple world semantics
slide: Section 8.2: Algebraic specification
In section 2,
we studied the algebraic specification
of abstract data types by means of
a signature characterizing producers
and observers.
We discussed the notions of equivalence
classes and initial models,
which consist of precisely the equivalence
classes that are needed.
,p>
Also, we looked at the interpretation of
objects as algebras, and we discussed
a multiple world semantics allowing for
dynamic state changes.
3
- data abstraction -- generators/observers matrix
- modules -- operation-oriented
- objects -- data-oriented
slide: Section 8.3: Decomposition -- modules versus objects
In section 3,
we looked at the various ways we may realize data abstractions
and we distinguished between a modular
approach, defining a collection of
operations, and a data-oriented approach,
employing objects.
4
- types -- syntactically, semantically, pragmatically
- compatible modifications -- type, signature, class
slide: Section 8.4: Types versus classes
Finally, in section 4, we discussed the differences
between a syntactic, semantic and operational
interpretation of types,
and how these viewpoints affect our
notion of refinement or compatible
modification.
object-oriented programming
- Characterize the differences between control
abstractions and data abstractions.
Explain how these two kinds of abstractions
may be embodied in programming language constructs.
- How can you model the meaning of abstract data types
in a mathematical way?
Do you know any alternative ways?
- Explain how types may affect object-oriented programming.
- Explain how you may characterize an abstract data type
by means of a matrix with generator columns and observer
rows.
What benefits does such an organization have?
- How would you characterize the differences between
the realization of abstract data types by modules
and by objects?
Discuss the trade-offs involved.
- How would you characterize the distinction
between types and classes?
Mention three ways of specifying types.
How are these kinds related to each other?
- How would you characterize behavior compatible
modifications?
What alternatives can you think of?
slide: Questions
object-oriented programming
There is a vast amount of literature on the algebraic
specification of abstract data types.
You may consult, for example, [Dahl92].
(C) Æliens
04/09/2009
You may not copy or print any of this material without explicit permission of the author or the publisher.
In case of other copyright issues, contact the author.