The correspondence relation
Instructor's Guide
intro,
types,
verification,
behavior,
objects,
composition,
summary,
Q/A,
literature
Behavioral refinement is not restricted
to the realization of abstract specifications.
We will now look at a definition of behavioral
refinement, following [Liskov93],
that may serve as a guideline for programmers
to define behavioral subtypes, both abstract and concrete,
including subtypes extending the behavioral
repertoire of their supertypes.
In [Liskov93] the relation between behavioral
types is explained by means of a so-called
correspondence mapping, that relates
a subtype to its (abstract) supertype.
- I(a,t,s) ≡ t = length(s) ∧t \geqslant 0 ∧s = α(a,t)
slide: The subtype correspondence mapping
A correspondence mapping is a triple
consisting of an abstraction function
- I(a,t,s) ≡ t = length(s) ∧t \geqslant 0 ∧s = α(a,t)
(that projects the values of the subtype
on the value domain of the supertype),
a renaming
- I(a,t,s) ≡ t = length(s) ∧t \geqslant 0 ∧s = α(a,t)
(that defines the relation between methods defined
in both types)
and an extension map
ξ (that defines the
meaning of additional methods).
See slide [10-correspondence].
Technically, the function
ξ must be onto, that is
each value of the supertype domain must be
representable by one or more values of the subtype domain.
Generally, when applying the abstraction function,
we loose information (which is irrelevant from
the perspective of the supertype), for
example the specific ordering of items in a container.
- dom( mτ ) \leqslant dom( mσ )
- ran( mσ ) \leqslant ran( mτ )
- pre( mτ )[ xτ : = α( xσ )] ⇒ pre( m σ )
- post( m σ ) ⇒ post( m τ )[ x τ : = α( x σ ) ]
- invariant( σ) ⇒
invariant( τ)[ x τ : = α( x σ ) ]
- φ\apijl x.m(a) φ′∧φ\apijl πm Ψ
⇒ α( x φ ) = α( x Ψ )
slide: Behavioral subtyping constraints
To determine whether a type
- dom( mτ ) \leqslant dom( mσ )
- ran( mσ ) \leqslant ran( mτ )
- pre( mτ )[ xτ : = α( xσ )] ⇒ pre( m σ )
- post( m σ ) ⇒ post( m τ )[ x τ : = α( x σ ) ]
- invariant( σ) ⇒
invariant( τ)[ x τ : = α( x σ ) ]
- φ\apijl x.m(a) φ′∧φ\apijl πm Ψ
⇒ α( x φ ) = α( x Ψ )
is a
(behavioral) subtype of a type
- dom( mτ ) \leqslant dom( mσ )
- ran( mσ ) \leqslant ran( mτ )
- pre( mτ )[ xτ : = α( xσ )] ⇒ pre( m σ )
- post( m σ ) ⇒ post( m τ )[ x τ : = α( x σ ) ]
- invariant( σ) ⇒
invariant( τ)[ x τ : = α( x σ ) ]
- φ\apijl x.m(a) φ′∧φ\apijl πm Ψ
⇒ α( x φ ) = α( x Ψ )
, one
has to define a correspondence mapping
- dom( mτ ) \leqslant dom( mσ )
- ran( mσ ) \leqslant ran( mτ )
- pre( mτ )[ xτ : = α( xσ )] ⇒ pre( m σ )
- post( m σ ) ⇒ post( m τ )[ x τ : = α( x σ ) ]
- invariant( σ) ⇒
invariant( τ)[ x τ : = α( x σ ) ]
- φ\apijl x.m(a) φ′∧φ\apijl πm Ψ
⇒ α( x φ ) = α( x Ψ )
and check the issues listed in slide [10-subtyping].
First, syntactically, we must check that the
signature of
- dom( mτ ) \leqslant dom( mσ )
- ran( mσ ) \leqslant ran( mτ )
- pre( mτ )[ xτ : = α( xσ )] ⇒ pre( m σ )
- post( m σ ) ⇒ post( m τ )[ x τ : = α( x σ ) ]
- invariant( σ) ⇒
invariant( τ)[ x τ : = α( x σ ) ]
- φ\apijl x.m(a) φ′∧φ\apijl πm Ψ
⇒ α( x φ ) = α( x Ψ )
and
- dom( mτ ) \leqslant dom( mσ )
- ran( mσ ) \leqslant ran( mτ )
- pre( mτ )[ xτ : = α( xσ )] ⇒ pre( m σ )
- post( m σ ) ⇒ post( m τ )[ x τ : = α( x σ ) ]
- invariant( σ) ⇒
invariant( τ)[ x τ : = α( x σ ) ]
- φ\apijl x.m(a) φ′∧φ\apijl πm Ψ
⇒ α( x φ ) = α( x Ψ )
satisfy the (signature)
subtyping relation defined in the previous chapter.
In other words, for each method m associated
with the object type
τ (which we call
τ),
and corresponding method
mσ
(which is determined by applying the renaming
ρ)
we must check the (contravariant) function subtyping
rule, that is
ρ
and
ran( mσ ) \leqslant ran( m τ ),
where ran is the range or result type of m.
Secondly, we must check that the behavioral
properties of
ran( mσ ) \leqslant ran( m τ ) respect those of
ran( mσ ) \leqslant ran( m τ ).
In other words, for each method m
occurring in
ran( mσ ) \leqslant ran( m τ ) we must check
that
ran( mσ ) \leqslant ran( m τ )
and that
ran( mσ ) \leqslant ran( m τ ).
Moreover, the invariant characterizing
σ
must respect the invariant
characterizing
σ,
that is
invariant( σ) ⇒ invariant( τ)[ x τ : = α( x σ ) ].
The substitutions
invariant( σ) ⇒ invariant( τ)[ x τ : = α( x σ ) ]
occurring in the behavioral rules are meant
to indicate that each variable of
type
τ must be replaced by a corresponding variable
of type
σ
to which the abstraction function is applied
(in order to obtain a value in the (abstract) domain of
σ).
And thirdly, in the final place,
it must be shown that the extension map
ξ is well-defined.
The extension map must be defined in such a way
that each method call for an object
x of type
ξ,
which we write as
x.m(a) where a represents
the arguments to the call,
is mapped to a program
πm in
which only calls appear to methods shared by
πm and
τ (modulo renaming)
or external function or method calls.
In addition the diamond rule
must be satisfied,
which means that the states
τ and
ψ resulting from
applying respectively
x.m(a) and
x.m(a)
in state
φ must deliver identical
values for x from the perspective of
φ,
that is after applying the abstraction function.
In other words, extension maps allow us to understand
the effect of adding new methods and to establish
whether they endanger behavioral compatibility.
In [Liskov93] a distinction is made between
constructors (by which objects are created),
mutators
(that modify the state or value of an object)
and observers
(that leave the state of an object unaffected).
Extension maps are only needed for mutator methods.
Clearly, for observer methods the result
of
ξ is empty, and constructors
are taken care of by the abstraction function.
Behavioral subtypes
The behavioral subtyping rules defined above
are applicable to arbitrary (sub)types, and not only
to (sub)types defined by inheritance.
As an example, we will sketch (still
following [Liskov93]) that a stack may be defined as
a behavioral subtype of the type bag.
Recall that a bag is a set allowing duplicates.
See slide [10-ex-subtype].
ξ
slide: Behavioral subtypes -- example
Let the type bag support the methods
ξ and
ξ and assume
that the type stack supports
the methods
ξ,
ξ and
in addition a method
settop(i:Int)
that replaces the top element of
the stack with its argument.
Now, assume that a bag is represented by
a pair
settop(i:Int), where
elems is a multiset (which is a set
which may contain multiple elements of the same value)
and bound is an integer indicating the maximal
number of elements that may be in the bag.
Further, we assume that a stack is represented as
a pair
〈 items, limit 〉 ,
where items is a sequence and limit is
the maximal length of the sequence.
For example
〈 items, limit 〉
is a legal value of bag
and
〈 1 ·2 ·7 ·1, 12 〉
is a legal value of stack.
The behavioral constraints for respectively the
method put
for bag
and push
for stack
are given as pre- and post-conditions in slide [10-ex-subtype].
To apply put, we require that the size
of the multiset is strictly smaller than the bound
and we ensure that the element i is inserted
when that pre-condition is satisfied.
The multi-set union operator
\uplus
is employed to add the new element to
the bag.
Similarly, for push we require the length
of the sequence to be smaller than the limit
of the stack
and we then ensure that the element is appended
to the sequence.
As before, we use the primed variables
\uplus and
s′
to denote the value of respectively
the bag b and the stack s after
applying the operations, respectively put and push.
Proceeding from the characterization of bag
and stack we may define the correspondence
mapping
s′ as in slide [10-ex-correspondence].
- α( 〈 items, limit 〉 ) =
〈 mk_set( items ), limit 〉
where
mk_set( ε ) = ∅
mk_set( e ·s ) = mk_set(s) \uplus {e}
- ρ( push ) = put, ρ( pop ) = get
- ξ(s.settop(i)) =
s.pop(); s.push(i)
slide: Behavioral subtypes -- correspondence
To map the representation of a stack to
the bag representation we use the function
- α( 〈 items, limit 〉 ) =
〈 mk_set( items ), limit 〉
where
mk_set( ε ) = ∅
mk_set( e ·s ) = mk_set(s) \uplus {e}
- ρ( push ) = put, ρ( pop ) = get
- ξ(s.settop(i)) =
s.pop(); s.push(i)
(which is inductively defined to map
the empty sequence to the empty set and to
transform a non-empty sequence into the union
of the one-element multiset of its first element
and the result of applying
- α( 〈 items, limit 〉 ) =
〈 mk_set( items ), limit 〉
where
mk_set( ε ) = ∅
mk_set( e ·s ) = mk_set(s) \uplus {e}
- ρ( push ) = put, ρ( pop ) = get
- ξ(s.settop(i)) =
s.pop(); s.push(i)
to the remaining
part).
The stack limit is left unchanged, since
it directly corresponds with the bound of the bag.
The renaming function
- α( 〈 items, limit 〉 ) =
〈 mk_set( items ), limit 〉
where
mk_set( ε ) = ∅
mk_set( e ·s ) = mk_set(s) \uplus {e}
- ρ( push ) = put, ρ( pop ) = get
- ξ(s.settop(i)) =
s.pop(); s.push(i)
maps push to put
and pop to get, straightforwardly.
And, the extension map describes the result of
- α( 〈 items, limit 〉 ) =
〈 mk_set( items ), limit 〉
where
mk_set( ε ) = ∅
mk_set( e ·s ) = mk_set(s) \uplus {e}
- ρ( push ) = put, ρ( pop ) = get
- ξ(s.settop(i)) =
s.pop(); s.push(i)
as the application of (subsequently)
- α( 〈 items, limit 〉 ) =
〈 mk_set( items ), limit 〉
where
mk_set( ε ) = ∅
mk_set( e ·s ) = mk_set(s) \uplus {e}
- ρ( push ) = put, ρ( pop ) = get
- ξ(s.settop(i)) =
s.pop(); s.push(i)
and
push(i).
- size( α(s). elems ) < α(s).bound
⇒
length( s.items ) < s.limit
- s′ = 〈 s.items ·i, s.limit 〉
⇒
α(s′) =
〈 α(s).elems \uplus {i}, α(s).bound 〉
slide: Behavioral subtypes -- proof obligations
With respect to the behavioral definitions given for
push and put we have to verify that
- size( α(s). elems ) < α(s).bound
⇒
length( s.items ) < s.limit
- s′ = 〈 s.items ·i, s.limit 〉
⇒
α(s′) =
〈 α(s).elems \uplus {i}, α(s).bound 〉
and that
post( push(i) ) ⇒ post( put(i) )[ b : = α(s) ].
These conditions, written out fully in slide [10-ex-proof],
are easy to verify.
Generally, a formal proof is not really necessary
to check that two types satisfy the behavioral
subtype relation.
As argued in [Liskov93],
the definition of the appropriate behavioral
constraints and the formulation of
a correspondence mapping is already a significant
step towards verifying that the types have the
desired behavioral properties.