WHAT DOES SUBSTITUTABILITY REALLY MEAN? PART 3
by Chris Date

 

 

 

 (Continued from Part 2)

 

This is the third part of a six-part series. In Part 1 and Part 2 I took a look at a couple of tutorial descriptions of the Liskov Substitution Principle (LSP) and found much fault with them. Now I want to turn my attention to the paper that appears to have been the origin of the LSP concept: A Behavioral Notion of Subtyping, by Barbara Liskov and Jeannette Wing (ACM Transactions on Programming Languages and Systems 16, No. 6, November 1994). Here is how I finished up Part 2:

 

“It was with a certain sense of relief and anticipation that I turned to the original Liskov/Wing paper. Surely here, I thought, I would find much greater precision and clarity of thinking and expression. Nor was I disappointed. Indeed, the paper was so clear that its errors were clear, too! In fact, it was precisely because I found I wanted to make so many comments on that paper that I decided to split the present article into three parts. In particular, it seemed best to defer my detailed commentary on the Liskov/Wing paper to Parts 4, 5 and 6, and that's what I'm going to do.”

 

Without further ado, let me start that commentary. 
 

 

Background Assumptions

 

The Liskov/Wing paper includes a section entitled "Model of Computation," in which the authors spell out some -- but unfortunately not all -- of their background assumptions. The present section represents my own attempt to distill out the essence of those assumptions. To be more specific, the work reported in the Liskov/Wing paper is explicitly cast in an object-oriented framework, and I believe the salient features of that framework are as indicated here:

 

·   Objects have values -- different values at different times, in general, unless the object in question is "immutable" (see below).

 

·   Objects are accessed via program variables containing pointers (i.e., object IDs). Program variables are subject to assignment, but objects apparently aren't (?).

 

·   Objects are generally "encapsulated," meaning they have behavior but no user-visible structure -- unless such structure is an intrinsic feature of the object type in question, as would be the case if, e.g., the object type in question were some array type (The paper doesn't actually say objects are encapsulated -- indeed, encapsulation as such isn't mentioned at all -- but I think it's at least implied.)

 

·   Every object is of some type. In fact, a given object is of exactly one type, except in the case where the object in question is of type S and type S is a proper subtype of type T, in which case the object is additionally of type T. (I believe the second sentence here is true, although the point is nowhere spelled out explicitly in the paper. Also, the term "proper subtype" doesn't appear, so I'd better define it: Type S is a proper subtype of type T if (a) it is a subtype of T and (b) S and T are distinct. (Note, therefore, that any given type T is a subtype of itself but not a proper one.))

 

·   Objects cannot change their type. (Again, this is my assumption--the paper doesn't say as much explicitly, but it does include numerous remarks that make sense only if what I've just said is correct.)

 

·   Objects are never destroyed. (I think this assumption -- which is stated explicitly -- is made purely to simplify other portions of the paper, but I can't be sure.)

 

·   Methods are bundled with types. As a consequence, every method has a distinguished parameter, called in the paper (rather loosely) "the method's object." Mutators are methods that update "the method's object," while observers are methods that don't (instead they return results -- i.e., values, not objects, I presume -- "of other types" (?)). However, the paper explicitly states that "an observer can also be a mutator," which I take to mean that a mutator can also return a result; also, I find it hard to imagine a mutator that doesn't "observe" as well. So the distinction between observers and mutators isn't very clear to me. However, the distinction, perhaps fortunately, isn't very important for present purposes.

 

·   Following on from the previous point, the paper explicitly defines any given type to consist, in part, of some specific set of methods. Note the (presumably intended) implication: Adding a new method to a given type changes the type! This fact would seem to have some rather important consequences, some of which I'll discuss in Parts 5 and 6 of this article.

 

·   An object is "immutable" if its value can never change (presumably such an object, unlike other objects, must be initialized when it first comes into existence). An object is "mutable" if it isn't immutable.

 

·   A type is immutable "if its objects are" (i.e., if and only if all of its objects are, presumably). A type is mutable if it isn't immutable. (This notion of types per se, as opposed to objects, being mutable or immutable seems rather strange to me. At the very least, there seems to be something wrong with the terminology; types per se are, among other things, sets of values, and such sets don't change -- in fact, sets of values are themselves values, and values cannot change, by definition. Also (quite apart from the terminology), I don't really understand what an "immutable type" would be (unless it's one you can't add methods to?) However, the rest of the paper has little to say regarding "mutable vs. immutable types," so perhaps the issue isn't very important.) 

 

 

Objectives

 

As I see it, the primary aim of the Liskov/Wing paper is not to propose an abstract model (as such) of subtyping and inheritance. Rather, its aim is to provide certain definitional constructs that allow assertions of the form "S is a subtype of T" to be formally verified,in the sense that:

 

a.      Methods associated with type S can be shown to "behave the same as" the corresponding methods associated with type T.

 

b.      "Constraints" associated with type S can be shown to imply those associated with type T.

 

Note: The paper makes these two statements much more precise. I can't do the same here, because I haven't explained enough of the background--and I don't want to explain more of the background just yet, because there are significant parts of it I don't agree with, as I'll make clear soon.

 

The paper's main contribution is thus that it provides a way of checking whether an assertion on the part of the type definer to the effect that S is a subtype of T is valid, or at least plausible. By way of motivation for their work, the authors offer the following remarks among others:

 

"[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."

 

Interestingly, this sentence (which as we saw in Part 1 was repeated in the Tockey article) seems to be as close as the paper comes to actually providing a statement of the Liskov Substitution Principle. Certainly there is no formal statement of LSP, as such, anywhere in the paper.

 

"Subtype Requirement: Let f(x) be a property provable about objects x of type T. Then f(y) should be true for objects y of type S where S is a subtype of T."

 

This statement is highlighted in the paper (in the introduction, in fact), and it constitutes clear evidence in support of what I said at the beginning of this section: namely, that the aim of the paper is to provide a mechanism that supports formal verification of claims to the effect that some given type S is a subtype of some other type T. (In case it's not obvious, I should stress the fact that I think this aim is a perfectly valid and interesting one. However, it's rather different from the one Hugh Darwen and I had in mind when we developed our own inheritance model, as I'll explain later. And I think the word "true" in the second sentence would better be replaced by the word "provable," but perhaps it's not important. 

"[We] were motivated primarily by pragmatics. Our intention [was] to capture the intuition programmers apply when designing type hierarchies in object-oriented languages. However, intuition in the absence of precision can often go astray or lead to confusion. This is why [in the past] it has been unclear how to organize certain type hierarchies such as integers."

 

[Sic! Presumably the authors mean "type hierarchies such as one involving different kinds of integers".]

 

I find these remarks about pragmatics and intuition very revealing. I could be quite wrong, but it seems to me that what Liskov and Wing are trying to do in their paper is retroactively to formalize and impose some discipline on a bunch of disparate preexisting notions that are all somehow related to some concept of "subtyping" and "type inheritance." The trouble is, there has never been--there still isn't!--any consensus on what these latter terms mean. Instead, what there definitely has been is much confusion... To quote from THE THIRD MANIFESTO once again: "In The Object-Oriented Database System Manifesto (Proc. 1st International Conference on Deductive and Object-Oriented Databases, Elsevier Science, 1990), Malcolm Atkinson et al. state that "[there] are at least four types of inheritance: substitution inheritance, inclusion inheritance, constraint inheritance, and specialization inheritance... Various degrees of these four types of inheritance are provided by existing systems and prototypes, and we do not prescribe a specific style of inheritance."

 

J. Craig Cleaveland, in his book AN INTRODUCTION TO DATA TYPES (Addison-Wesley, 1986) states that "[inheritance can be] based on [a variety of] different criteria and there is no commonly accepted standard definition" -- and proceeds to give eight (!) possible interpretations. (Bertrand Meyer, in his article The Many Faces of Inheritance: A Taxonomy of Taxonomy, IEEE Computer 29, No. 5, May 1996, gives twelve.)

 

In a Technical Correspondence item in Communications of the ACM 37, No. 9 (September 1994), Kenneth Baclawski and Bipin Indurkhya state that "a programming language [merely] provides a set of [inheritance] mechanisms. While these mechanisms certainly restrict what one can do in that language and what views of inheritance can be implemented [in that language], they do not by themselves validate some view of inheritance or other. [Types,] specializations, generalizations, and inheritance are only concepts, and... they do not have a universal objective meaning... This [fact] implies that how inheritance is to be incorporated into a specific system is up to the designers of [that] system, and it constitutes a policy decision that must be implemented with the available mechanisms." In other words, there simply is no model. "

 

Thus, it seems to me that most if not all of those earlier notions of subtyping and inheritance were confused and muddled at best. And it also seems to me that at least some of that muddle has, regrettably, carried over to the Liskov/Wing paper. I'll get more specific on this point below, as well as later in this series.

 

Anyway, regardless of whether I'm right about that business of "formalizing and imposing discipline on a bunch of preexisting notions," I would like to emphasize how different our own objectives were when we (i.e., Hugh Darwen and I) developed our own inheritance model. Basically, we worked from first principles; we very deliberately paid very little attention to existing work in the field (After a period of initial study, that is. Of course, we did recognize that our decision to work from first principles meant we might be duplicating work already done by somebody else, but there seemed to be so much confusion in the field--not least the confusion over whether a circle was an ellipse!--that we decided we would be better off going it alone, as it were. And so we did, and I think we were). We certainly didn't feel constrained by "existing programmer intuition," and very definitely not by "object-oriented languages." Rather, we were concerned with getting the concepts of the model right first-- i.e., finding a good answer to the question "What does it mean to assert that S is a subtype of T?"--before possibly turning our attention to the question of making such assertions formally verifiable in some way. Thus, we started by developing a formal theory of types as such (and considering the impact of such a theory on the relational model of data in particular). Then we extended that theory to incorporate what seemed to us to be logically sound and useful concepts of subtyping and inheritance. In particular -- since we were familiar with at least the general idea and aims of LSP, albeit not by that name -- we took pains to ensure that our theory did indeed support that notion in what seemed to us to be a logically sound, correct, and useful fashion.

 

In this connection, the Liskov/Wing paper contains another interesting remark: "Our work is most similar to that of America [P. America: Designing an Object-Oriented Programming Language with Behavioural Subtyping, in J. W. de Bakker, W. P. de Roever, and G. Rozenberg (eds.), FOUNDATIONS OF OBJECT-ORIENTED LANGUAGES (Springer Verlag, 1991)]… who has proposed rules for determining based on type specifications whether one type is a subtype of another." In other words, the idea seems to be: Given two type specifications, what conditions must those specifications satisfy for one of the two types to be regarded as a subtype of the other? In our own work, by contrast, we took the diametrically opposite approach: We assumed that type S was a subtype of type T, then we spelled out in detail what the properties of type S must logically be, and went on to explore the logical implications of those properties (especially the substitutability implications). I'll come back to this difference in approach later in this series, when I discuss the "bags-and-stacks" example. 

 

Next, the Liskov/Wing Inheritance Model.

 

(Continued in Part 4)

 

 

Posted 07/21/02

 

 

 

[ABOUT] [QUOTES] [LINKS]