WHAT DOES SUBSTITUTABILITY REALLY MEAN? PART 1
by Chris Date

 

 

 

Introduction

 

This is the first part of a six-part investigation into the subject of type inheritance--in particular, into the crucial notion of substitutability.  As you might know, I published another article on this subject earlier this year entitled Is a Circle an Ellipse?.  In that article, I argued that circles are ellipses in the real world, and hence that any model of type inheritance that failed to regard a circle as an ellipse could hardly be claimed to be "a good model of reality."  I also stated that Hugh Darwen and I had defined an inheritance model in which a circle certainly was an ellipse, and further that we thought we knew how to implement that model efficiently (see FOUNDATION FOR FUTURE DATABASE SYSTEMS: THE THIRD MANIFESTO, 2nd edition, hereafter referred to simply as THE THIRD MANIFESTO). 

 

I published that previous article in July 2001.  Then I ducked ... Perhaps it's an exaggeration to say that people threw things at me from all directions, but I certainly received an unusually large amount of correspondence, most of it unfavorable.  It isn't worth giving a blow-by-blow response here to all of the criticisms I received, but I do want to respond to one particular claim that ran like a common thread through many of them: namely, that I seemed not to be aware of the Liskov Substitution Principle--or (worse!) if I was aware of it, then I didn't understand it. 

 

Well, I freely admit that I wasn't familiar with the term "Liskov Substitution Principle" (hereinafter abbreviated to LSP) when I wrote my original article.  However, I was certainly familiar--I venture to say, extremely familiar--with the concept, which I had been taught to refer to as substitutability.*  Here's a loose definition (I'll refine this definition at the very end of Chapter 16): 

 


If S is a subtype of T, then wherever the system expects a value of type T, a value of type S can be substituted


 

For example, if AREA is an operator that can be invoked on an ellipse e, then we can always substitute a circle c for e in such an invocation, because circles are ellipses (at least, such is my claim). 

 

* So perhaps I owe Barbara Liskov an apology, if the concept truly is due to her and I haven't acknowledged that fact in my previous writings. 

 

As I recall, it was Nelson Mattos of IBM who introduced me to the idea of substitutability, way back in 1993; certainly I was already writing about it no later than May of that year, when I was working on the 6th edition of my book AN INTRODUCTION TO DATABASE SYSTEMS (published by Addison-Wesley in 1994, though with a copyright date of 1995).  And I have subsequently studied it in considerable depth in connection with my work with Hugh Darwen on the inheritance model mentioned above (the one described in THE THIRD MANIFESTO). 

 

 

What Is LSP? 

 

Given all of the foregoing, I naturally wanted to see if there were any significant differences between substitutability as I understood it and LSP.  I began my attempt to answer this question by taking a look at a short article by Stephen R. Tockey entitled What is LSP? That article starts by asserting that LSP is described in detail in a paper entitled A Behavioral Notion of Subtyping, by Barbara Liskov and Jeannette Wing (ACM Transactions on Programming Languages and Systems 16, No. 6, November 1994), and it goes on to quote from the abstract to that paper as follows.  (Note:  In fact, the extract quoted consists of the first half of the abstract in its entirety.) 

 

The use of hierarchy is an important component of object-oriented design.  Hierarchy allows the use of type families, in which higher level supertypes capture the behavior that all of their subtypes have in common.  For this methodology to be effective, it is necessary to have a clear understanding of how subtypes and supertypes are related.  This paper takes the position that the relationship should ensure that any property proved about supertype objects also holds for subtype objects.  

 

The wording of this extract put me on my guard right away.  I believe strongly--and I've said as much, in many places and on many occasions--that if you want to make precise and formal statements about data and data management in general, then it's a bad idea to try and do so in terms of "objects."  Object terminology almost always seems to be fuzzy.  There are several reasons for this state of affairs, but one of the biggest is that it (object terminology, that is) seems never to make the absolutely crucial distinction between values and variables.  (In fact, in our own work on type inheritance, Hugh Darwen and I found it necessary to distinguish two kinds of substitutability, one based on values and the other based on variables; what's more, we showed that if you fail to make that distinction, then at least one, and arguably as many as four, extremely undesirable consequences follow.  I'll elaborate on this point in the subsequent parts of this article. 

 

Anyway (I said to myself as I was reading), perhaps the Liskov/Wing paper does deal with the substitutability issue properly and does avoid the many "value vs. variable" traps and confusions.  We'll see.  Meanwhile, I was still studying the Tockey article.  The very next thing it said was this: 

 

Very simply put, the LSP states that "objects of the subtype ought to behave the same as those of the supertype as far as anyone or any program using supertype objects can tell."  In other words, I ought to be able to substitute any subtype of class X in a program that expects a member of class X and the program should still behave reasonably. 

 

As I expected:  Fuzziness!  Let's examine the two sentences in this quote one at a time. The first one is not too bad*--I mean, you can make a pretty good guess at what the author is trying to say, even if, like me, you feel the phrase "ought to behave" would be better shortened to just "behave"--but surely it could be better expressed?  For example: 

 

If S is a subtype of T, then wherever the system expects an object of type T, an object of type S can be substituted. 

 

Much more precise, and--at least to me--much easier to understand, too.  (Mind you, I don't necessarily agree with the sentiment the revised sentence expresses!--it depends, of course, on what the term object means.  But let that pass for now.) 

 

* I subsequently discovered that this first sentence was a direct quote from the Liskov/Wing paper. 

 

Now let's look at the second sentence:  "In other words, I ought to be able to substitute any subtype of class X in a program that expects a member of class X and the program should still behave reasonably."  I have many reactions to this sentence! 

 

Ø       First of all, where did that stuff about classes come from?  It wasn't mentioned in the first sentence, and it wasn't mentioned in the Liskov/Wing abstract either.  Am I to understand that class is just another word for type?  If so, why introduce the term?  If not, how do the concepts differ?  And if they do differ, what are we supposed to make of the phrase "any subtype of class X"? 

 

By the way, in my original article (Is a Circle an Ellipse?) I did say that class and type were synonyms--and I got a lot of flak on that one, too. The truth is, however, that there's an amazing amount of confusion over this particular issue.  In support of this claim, let me quote some material from Appendix K of THE THIRD MANIFESTO. Appendix K is the References and Bibliography appendix, and it includes a reference to a book by Elisa Bertino and Lorenzo Martino entitled OBJECT-ORIENTED DATABASE SYSTEMS: CONCEPTS AND ARCHITECTURES (Addison-Wesley, 1993).  Here's the annotation to that reference from Appendix K (edited just slightly here):* 

 

* I quoted from this annotation in my original article, too. 

 

Many publications from the object world try to draw a distinction (as we do not) between type and class, and Bertino and Martino's book is one such:  "Object-oriented systems can be classified into two main categories--systems supporting the notion of class and those supporting the notion of type ... [Although] there are no clear lines of demarcation between them, the two concepts are fundamentally different [sic!] ... Often the concepts type and class are used interchangeably.  However, when both are present in the same language, the type is used to indicate the specification of the interface of a set of objects, while class is an implementation notion [so why is it "present in the language" at all?].  Therefore ... a type is a set of objects which share the same behavior ... [and] a class is a set of objects which have exactly the same internal structure and therefore the same attributes and the same methods.  [But if all objects in a class have the same attributes and the same methods,* is not that class a type, by the authors' own definition?]  The class defines the implementation of a set of objects, while a type describes how such objects can be used."  (Contrast the ODMG specification, incidentally--see R. G. G. Cattell and Douglas K. Barry (eds.), THE OBJECT DATA STANDARD: ODMG 3.0, Morgan Kaufmann, 2000--which uses the terms type and class in almost exactly the opposite way.) 

 

* I prefer the term operator, but method is much more common in the object world, and so I'll use it here. 

 

The authors [i.e., Bertino and Martino] then go on to say:  "With inheritance, a class called a subclass can be defined on the basis of the definition of another class called a superclass."  Surely--in accordance with their own earlier definitions--they should be talking in terms of types here, not classes?  And then they add:  "The specification hierarchy (often called subtype hierarchy) expresses ... subtyping relationships which mean that an instance of the subtype can be used in every context in which an instance of the supertype can correctly appear (substitutability)."  Observe that they do now speak of types, not classes ... [Also,] observe the failure to distinguish properly between values and variables (note the fuzzy talk of "instances"), and the consequent failure to distinguish between value substitutability and variable substitutability.

 

Of course, it is precisely because of confusions, terminological and otherwise, such as those just illustrated that we felt free--or compelled, rather--to introduce our own terms in THE THIRD MANIFESTO and to define them as carefully as we could.

 

Ø       Back to the sentence from the Tockey article.  My next complaint is sloppiness, as evidenced by the phrasing "substitute any subtype of class [= type?] X."  To be specific, the author is talking about substituting a subtype as such, when presumably what he should be talking about is substituting an object of the subtype in question.  This is not just a quibble!  As I wrote in my original article, our own model of types and inheritance is based on the four classical--and, we hope, widely understood--concepts type, value, variable, and operator.*  There are HUGE logical differences between any two of these concepts, and any article or other writing that confuses them is likely to manifest other confusions as well. 

 

* I also said we found no need to drag in any kind of "object" concept at all, and I stand by this claim. 

 

Ø       By now you've probably forgotten the sentence from Tockey's article that I'm complaining about (I nearly have myself), so let me repeat it again, but with a different emphasis this time: 

 

In other words, I ought to be able to substitute [an object of?] any subtype of class [= type?] X in a program that expects a member of class [= type?] X and the program should still behave reasonably. 

 

Am I to understand that member is just another word for object?  If so, why introduce the term?  If not, how do the concepts differ? 

 

I could say quite a bit more about this sentence of Tockey's--in particular, I'd love to see a definition of what it means for a program to "behave reasonably"--but let's move on.  A couple of paragraphs further on, I found this: 

 

In order for class X' to be a subtype [sic!] of class X, the pre-conditions of the methods of the supertype [presumably "the supertype" is class X?] must be at least as restrictive (but possibly more) than the pre-conditions of the corresponding methods of the subtype(s) AND/OR the post-conditions of the methods of the subtype must be at least as restrictive (but possibly more) than the post-conditions of the corresponding methods on the supertype. 

 

Well, the syntax here is pretty wobbly, but let's focus on the two central claims the author appears to be making.  I'll take them one at a time.  The first is as follows: 

 

·   Let M be a method that applies to objects of type X and therefore--let's assume--to objects of type X' as well.*  Then the preconditions that apply to M when it's invoked on an object of type X must be "at least as restrictive" as those that apply to M when it's invoked on an object of type X'. 

 

* Actually this assumption needs to be carefully examined too, but this isn't the place for that examination.  Suffice it to say that the distinction between values and variables rears its (very nonugly) head here once again. 

 

    To me, this claim seems to be nonsense.  For consider: 

 

§   First of all, by definition, there are more values of type X than there are of type X'.  After all, to say that X' is a subtype of X is to say that every value of type X' is a value of type X, so there can't be MORE values of type X' than there are of type X; and if the two types have the same number of values, then they're the same type.  Note:  I'm aware that not everyone agrees with the points I'm making here.  I'll revisit them in the next chapter. 

 

§   The precondition that applies to M when it's invoked on an object O of type X is basically that O must have a value that's a value of type X.  The precondition that applies to M when it's invoked on an object O' of type X' is basically that O' must have a value that's a value of type X'.  This latter precondition is OBVIOUSLY more restrictive than the former, thereby directly contradicting the claim that the former is supposed to be more restrictive than the latter.  Note:  Of course, I'm aware that preconditions might be more complicated in practice than the simple ones I'm describing here, but it's sufficient to consider just the simple case in order to make the point I want to make.* 

 

* As an aside, I feel bound to say that I'm not very happy with the terminology of "preconditions," anyway.  Given a method M, the preconditions, as such, for M are surely always the same, no matter what types the arguments might happen to have.  Thus, the real question is, rather, "Do the arguments satisfy the preconditions?"  (Perhaps the reason I'm having difficulties here is that Tockey and I are basing our statements on different unspoken assumptions.  Be that as it may, I'll revisit the whole business of preconditions--and postconditions--in Chapter 16.) 

 

Now here's the second claim: 

 

·   Again, let M be a method that applies to objects of type X and therefore to objects of type X' as well.  Then the postconditions that apply to M when it's invoked on an object of type X' must be "at least as restrictive" as those that apply to M when it's invoked on an object of type X. 

 

This claim, by contrast, does seem reasonable.  Assume first that M produces a result R when it's invoked.  Then--given the preconditions stated earlier--it's obvious that every possible result that can be produced when invoking M on some object O' of type X' can also be produced when invoking M on some object O of type X (because O' is an object of type X, by definition).  Thus, the postcondition that applies to M when it's invoked on an object of type X' is obviously "at least as restrictive" as the one that applies to M when it's invoked on an object of type X. 

 

Onward.  Tockey subsequently gives an example of two methods both called Draw, one of which applies to objects of type GeometricShape and the other to objects of type WildWestGunfighter, and observes, correctly, that there's no subtyping, and therefore no substitutability, in that example.  (His actual words are as follows:  "[According] to LSP, WildWestGunfighter is not a subtype of GeometricShape and I should not expect a program to be well behaved if I substitute a member of WildWestGunfighter in a place where the program expected a GeometricShape.")  Well, I agree, but I think the point would have been much clearer if the writer had explicitly introduced the terms inclusion polymorphism and overloading polymorphism (and explained the difference between them, of course).  Let me elaborate. 

 

First of all, an operator (or "method") is said to be polymorphic if it's defined in terms of some parameter P and the arguments corresponding to P can be of different types on different invocations.  The equality operator "=" is an obvious example:  We can test any two values for equality (just so long as the two values are of the same type), and so "=" is polymorphic--it applies to integers, and to character strings, and to ellipses, and to polygons, and in fact to values of every type.  Analogous remarks apply to the assignment operator ":=" also.  Further examples include the well-known aggregate operators (MAX, COUNT, etc.), the operators of the relational algebra (UNION, JOIN, etc.), and others. 

 

Next, polymorphism comes in (at least) two distinct flavors, known as inclusion polymorphism and overloading polymorphism, respectively.*  To paraphrase some remarks from THE THIRD MANIFESTO, a helpful way of characterizing the difference between these two concepts is as follows: 

 

* The kind of polymorphism displayed by the operators of the relational algebra is called generic polymorphism, on the grounds that--loosely speaking--those operators apply to all possible relations, generically. 

 

Ø       Inclusion polymorphism means we have one operator with several distinct implementation versions under the covers (but the user does not need to know that the versions in question are in fact distinct--to the user, there is just the one operator).  Inclusion polymorphism is what we get with subtyping, and it implies substitutability.  For example, the fact that the operator AREA applies to values of type POLYGON implies that the same operator AREA can be invoked on a value of type RECTANGLE (i.e., rectangles can be substituted for polygons), even if there are indeed--as there probably will be, for efficiency reasons--two distinct implementation versions of AREA under the covers.  Note:  The term "inclusion polymorphism" derives from the fact that, e.g., the set of all rectangles is included in the set of all polygons (just as the set of all circles is included in the set of all ellipses). 

 

Ø       Overloading polymorphism means we have several distinct operators with the same name (and the user does need to know that the operators in question are in fact distinct, with distinct--though preferably similar--semantics).  Overloading polymorphism has nothing to do with subtyping, and it doesn't imply substitutability.  It shouldn't really even be mentioned in connection with subtyping, except to make it clear (as I'm trying to do here) that it has nothing to do with the subject under discussion.  Tockey's "Draw" example is an example of overloading.* 

 

* What's more, that example violates the suggested principle that the "several distinct operators" in question should have similar semantics.  Indeed, the kind of polymorphism displayed in that example might better be called punning polymorphism. 

 

Note:  The foregoing definitions notwithstanding, I should now warn you that some writers--very unfortunately, in my opinion--use the term overloading to mean inclusion polymorphism.  Caveat lector! 

 

(Continued in Part 2)

 

 

Posted 05/31/02