Object inheritance -- dynamic binding
where and
Simple typing --
Delayed --
We have \zline{(more information)}
slide: Object inheritance -- dynamic binding
Let (parent) P and (child) C be defined as above.
Now, if we know that the type of is
then we may simply characterize as being
of type .
However, when we delay the typing of the P
component (by first composing the record specifications
before abstracting from self)
then we may obtain
as the type of .
By employing the refinement rule and unrolling we can
show that .
Hence, delayed typing clearly provides more information
and must be considered as the best choice.
Note, however, that both and hold.
See slide [9-self-dynamic].
A second, important question that emerges
with respect to inheritance is how
self-reference affects the subtyping relation between
object specifications related by inheritance.
Consider the object specifications P and C given
in slide [9-self-contravariance].
In the (derived) specification C, the method eq
is redefined to include an equality test for the b
component.
However, when we determine the object types corresponding
to the specifications P and C
we observe that .
Contravariance
-
,
where
where
However \zline{(subtyping error)}
slide: Object inheritance -- contravariance
The reasoning is as follows.
For and , we have
that
which is (by unrolling) equal to .
Now suppose that , then we have that
is a subtype of
which is true when
and hence (by contravariance) when .
Clearly, this is impossible. Hence .
We have a problem here,
since the fact that means that the type checker
will not be able to accept the derivation of C from P,
although C is clearly dependent on P.
The solution to our problem
lies in making the type dependency involved in deriving
C from P explicit.
Notice, in this respect, that in the example above we
have omitted the type of the abstraction variable
in the definition of eq,
which would have to be written as
(and in a similar way for C ) to do it properly.
Type dependency
The expression self is essentially of a polymorphic nature.
To make the dependency of object specification on self
explicit, we will employ an explicit type variable
similar as in .
Let stand for
as before.
We may regard as a type function, in the sense
that for some type the expression
results in a type.
To determine the type of an object specification
we must find a type that satisfies both
and .
Type dependency -- is polymorphic
- Let \zline{(type function)}
-
-
F-bounded constraint \n
Object instantiation:
for \n
We have because
slide: Bounded type constraints
We may write an object specification as
,
which is typed as .
The constraint that , which is called
an F-bounded constraint, requires
that the subtype substituted for is
a (structural) refinement of the record type .
As before, we have that with ,
which differs from our previous definition only
by making the type dependency in P explicit.
See slide [9-dependency].
Now, when applying this extended notion of object
specification to the characterization of inheritance,
we may relax our requirement that must be a subtype
of into the requirement that
for any , where F is the record specification
of P and G the record specification of C.
Inheritance
with recursive types
Valid, because
However
slide: Inheritance and constraints
For example, when we declare
and as in slide [9-self-constraint],
we have that
for every value for .
However, when we find types and
such that and
we (still) have that .
Conclusion, inheritance allows more than subtyping.
In other words, our type checker may guard
the structural application of inheritance, yet will
not guarantee that the resulting object types
behaviorally satisfy the subtype relation.
Discussion -- Eiffel is not type consistent
We have limited our exploration of the recursive structure
of objects to (polymorphic) object variables.
Self-reference, however, may also occur to class variables.
The interested reader is referred to [CoHC90].
The question that interests us more at this particular
point is what benefits we may have from the techniques employed
here and what lessons we may draw from applying them.
One lesson, which should not come as a surprise,
is that a language may allow us to write programs
that are accepted by the compiler yet are behaviorally incorrect.
However, if we can determine syntactically that the
subtyping relations between classes is violated we may at
least expect a warning from the compiler.
So one benefit, possibly, is that we may improve our compilers
on the basis of the type theory presented in this chapter.
Another potential benefit is that we may better understand the trade-offs
between the particular forms of polymorphism offered by our
language of choice.
The analysis given in [CoHC90] indeed leads to a
rather surprising result.
Contrary to the claims made by its developer,
[CoHC90] demonstrate that Eiffel is not type consistent.
The argument runs as follows.
Suppose we define a class C with a method eq
that takes an argument of a type similar
to the type of the object itself
(which may be written in Eiffel as like Current).
We further assume that the class P is defined
in a similar way, but with an integer field i
and a method eq that tests only on i.
See slide [9-self-eiffel].
Inheritance != subtyping
Eiffel
class C inherit P redefine eq
feature
b : Boolean is true;
eq( other : like Current ) : Boolean is
begin
Result := (other.i = Current.i) and
(other.b = Current.b)
end
end C
slide: Inheritance and subtyping in Eiffel
We may then declare variables v and p of type P.
Now suppose that we have an object c of type C,
then we may assign c to v and invoke the method
eq for v, asking whether p is equal to v, as in
p,v:P, c:C
v:=c;
v.eq(p); // error p has no b
slide: Example
Since v is associated with an instance of C,
but syntactically declared as being of type P,
the compiler accepts the call.
Nevertheless, when p is associated with an instance of
P trouble will arise, since (due to dynamic binding)
the method eq defined for C will be invoked while
p not necessarily has a field b.
When we compare the definition of C in Eiffel
with how we may define C in C++,
then we are immediately confronted with
the restriction that we do not have such a dynamic typing mechanism
as like Current in C++.
Instead, we may use overloading, as shown
in slide [9-self-cc].
class C : public P { C++
int b;
public:
C() { ... }
bool eq(C& other) { return other.i == i && other.b == b; }
bool eq(P& other) { return other.i == i; }
};
slide: Inheritance and subtyping in C++
When we would have omitted the P variant
of eq, the compiler complains about hiding a virtual
function.
However, the same problem arises when we define eq
to be virtual in P, unless we take care to
explicitly cast p into either a C or P reference.
(Overloading is also used in [Liskov93]
to solve a similar problem.)
In the case we choose for a non-virtual definition
of eq, it is determined statically which variant
is chosen and (obviously) no problem occurs.
Considering that determining equality between
two objects is somehow orthogonal to
the functionality of the object proper,
we may perhaps better employ externally defined
overloaded functions to express relations
between objects.
This observation could be an argument to
have overloaded functions apart from
objects, not as a means
to support a hybrid approach but as a means
to characterize relations between objects
in a type consistent (polymorphic) fashion.
object-oriented programming
Summary
object-oriented programming
This chapter has treated polymorphism
from a foundational perspective.
In section 1,
we looked at abstract inheritance as
employed in knowledge representation.
1
- abstract inheritance -- declarative relation
- inheritance networks -- non-monotonic reasoning
- taxonomic structure -- predicate calculus
slide: Section 9.1: Abstract inheritance
We discussed the non-monotonic aspects
of inheritance networks
and looked at a first order logic
interpretation of taxonomic
structures.
2
- types -- sets of values
- the subtype relation -- refinement rules
- functions -- contravariance
- objects -- as records
slide: Section 9.2: The subtype relation
In section 2,
a characterization of types as sets of values
was given.
We looked at a formal definition of the
subtype relation and discussed
the refinement rules for functions and objects.
3
- typing -- protection against errors
- flavors -- parametric, inclusion, overloading, coercion
- inheritance -- incremental modification mechanism
slide: Section 9.3: Flavors of polymorphism
In section 3,
we discussed types as a means to prevent errors,
and distinguished between various flavors
of polymorphism,
including parametric polymorphism,
inclusion polymorphism, overloading and
coercion.
Inheritance was characterized as an incremental
modification mechanism, resulting in
inclusion polymorphism.
4
- subtypes -- typed lambda calculus
- overloading -- intersection types
- bounded polymorphism -- generics and inheritance
slide: Section 9.4: Type abstraction
In section 4,
some formal type calculi were presented,
based on the typed lambda calculus.
These included a calculus for simple subtyping,
a calculus for overloading,
employing intersection types,
and a calculus for bounded polymorphism,
employing type abstraction.
Examples were discussed illustrating the (lack of) features
of the C++ type system.
5
- hiding -- existential types
- packages -- abstract data types
slide: Section 9.5: Existential types
In section 5,
we looked at a calculus employing
existential types, modeling abstract
data types
and hiding by means of packages
and type abstraction.
6
- self-reference -- recursive types
- object semantics -- unrolling
- inheritance -- dynamic binding
- subtyping -- inconsistencies
slide: Section 9.6: Self-reference
Finally, in section 6, we discussed self-reference
and looked at a calculus employing recursive
types.
It was shown how object semantics may be determined
by unrolling, and we studied the semantic
interpretation of dynamic binding.
Concluding this chapter, an example was given
showing an inconsistency in the
Eiffel type system.
object-oriented programming
- How would you characterize inheritance
as applied in knowledge representation?
Discuss the problems that arise
due to non-monotony.
- How would you render the meaning of
an inheritance lattice?
Give some examples.
- What is the meaning of a type?
How would you characterize the relation between
a type and its subtypes?
- Characterize the subtyping rules for ranges,
functions, records and variant records.
Give some examples.
- What is the intuition underlying the
function subtyping rule?
- What is understood by the notion
of objects as records?
Explain the subtyping rule for objects.
- Discuss the relative merits of typed
formalisms and untyped formalisms.
- What flavors of polymorphism
can you think of?
Explain how the various flavors are
related to programming language constructs.
- Discuss how inheritance may be understood
as an incremental modification mechanism.
- Characterize the simple type calculus
, that is the syntax, type
assignment and refinement rules.
Do the same for
and .
- Type the following expressions:
(a) ,
(b) ,
and
(c) .
- Verify whether:
(a) ,
(b) , and
(c) .
- Explain how you may model abstract
data types as existential types.
- What realizations of
the type
can you think of?
Give at least two examples.
- Prove that
.
- Prove that
, for .
slide: Questions
object-oriented programming
As further reading I recommend
[CW85] and [Pierce93].
As another source of material
and exercises consult [Palsberg94].
[BG93] contains a number of relevant papers.
An exhaustive overview of the semantics of object systems,
in both first order and second order calculi, is further
given in [ObjectCalculus].
(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.