From: jbm@jbm.nada.kth.se (Jonas Mölsä) Newsgroups: comp.object Subject: Subtypes, values, and variables Date: 26 Feb 1996 19:46:43 GMT Organization: Royal Institute of Technology, Stockholm, Sweden This article is the result of several hours of work at trying to make the subject matter understandable. I really do think that it says some important things. Read slowly and hopefully you will se what I mean. A Circle Is An Ellipse, Even For Programmers. ============================================ 1. Introduction In this article I intend to show that under very natural interpretations of the concept of subtype, a circle is indeed a kind of ellipse, even in the programming community. My hopes are to dispel some of the confusion surronding subtypes and Liskov's substitution principle. The main point of view we have to take to see this, is that variables and values are different entities. The main obstacle will turn out to be familiar syntax (and the way syntax shapes our minds). The examples will be in pseudo C++, simply because C++ is for me a familiar language. The points made in this article, however, are valid regardless of the language chosen to illustrate the examples. 2. Background Information In this article, we are only interested in the semantics of our language, because we are focusing on what our programs mean. Values and variables are different entities. Variables are boxes used to store values. Variables are unique. Values are not unique. Two different variables containg 42 are still two different variables. Two occurrences of the value 42 are not two different values. The storing aspect of variables will emphasized by using the name "box" instead of "variable" in this article. The row of boxes that a computation uses will be called the state vector. Functions will be restricted to be mathematical functions. Because of this, functions will be free of side effects. They take some values as arguments and return a value as result. That is, they are mappings from one set of values to another set of values. A procedure that affects a box "a" *could* be viewed as an assignment to "a" of the new value, computed by a function. That is, "p(a)" is equal to "a = f(a)" where f is a function. "p(a, b)" is equal to "(a, b) = f (a, b)" if "p" changes both of its arguments. Say that we have an object "o" of type "X". Instead of writing "o.p(a)" we will write "p(o, a)" but we will keep the semantics (virtual binding for "o" etc) Again, notice that "p(o, a)" is equal to "o = f(o, a)" for some function f. This means that since "o.p(a)" is semantically equivalent to "o = f(o, a)" we can use the latter form without fear for loss of expressive power or loss of semantics. 3. What I am trying to say What I am trying to say is, that the confusion of whether a circle is a kind of ellipse or not, steems to a large part from the syntax of the statement "circle.mult_major_axis(10);" where "mult_major_axis" is inherited from ellipse and enlarge the major axis of the object. This statement is usually taken as proof of the "fact" that circles are not ellipses, because the statement is obviously meaning- less for circles. If we rewrite it in two steps according to the above conventions, we will se what is wrong. circle.mult_major_axis(10); == mult_major_axis (circle, 10); == circle = mult_major_axis (circle, 10); // here, mult...() is a function In the last line, mult_major_axis takes a circle and 10 as arguments and returns an ellipse as return value. Now it is obvious what is wrong with the first statement. We are trying to assign an ellipse to a circle. Doesn't that prove that circles ar not ellipses? No, it proves that boxes for storing circles are not boxes for storing ellipses (which is rather obvious). So what's the story? The important thing here is to keep separate the notion of boxes and values. Cirlce values are definitely a kind of ellipse values. Functions that accept ellipses as arguments happily accept circles. Remeber that "o.p(a)" is semantically equivalent to "o = f(o, a)"? If we stick to the latter form (that is, all methods are mathematical functions and assignment is the only thing allowed to change the state vector) then it is quite easy to see that: **************************************************************** **** Subtype values can be used in all contexts where **** **** values of a supertype can be used. **** **************************************************************** This is simply the most important point in this article. If mult_major_axis accepts ellipses, it will also accept circles. In both cases the result will be an ellipse (which sometimes happens to have equal lengths of the major and minor axis). This line of reasoning also has som interesting consequenses for argument passing, but that will have to wait till another article. 4. Put Up or Shut Up a) By separating boxes from values, it becomes impossible to make mixing misstakes. In my experience, this is one of the most common sources of confusion, and one which have very large effects on the quality of both the model and the program. (At least for small to medium sized projects.) b) It makes the confusion go away without losing any expressive power. Clarity and expressive power all in one. c) If we do not think about our types in this way, it means that most of our subtypes will be broken from the start. - Let's say that the supertype has method wich can change the state of the object. Let's also say that the semantics specify that for values belonging to the subtype, the result is required to belong to the subtype. In this case, everything is fine. For the subtype, the method will naturally have a domain consisting of subtype objects, but the semantics will be unchanged. - Let's instead say that the semantics specify that for values belonging to the subtype, the result is not required to belong to the subtype. In this case, our subtype is broken. We cannot change the range of the function without changing its semantics. Thus, even for subtype objects, the result may be an object that does not belong to the subtype. Since this is forbidden ("circle.mult_major_axis(10);" is meaningless) we cannot call that method. This means that we cannot use objects of the subtype in every situation where we can use objects of the supertype. As stated above: Our subtype is broken. 5. Objection, Your Honour - The syntax is strange, and no one will write it that way. The syntax is semantically equivalent to the usual syntax. It is used here to highlight what is really going on. The syntax is not important here, it is the way of thinking about these things that is important. - You cannot separate boxes from values in object oriented languages like C++ or other similar languages, because objects are boxes *with* values. Yes, you can. It is precisely this mixing that causes a lot of confusion. We have boxes which can store values, and we have values. Let's call both of these different creatures "objects" and watch everyone getting confused :-) It is like referring to both the vehicle and its indoor parking place as "Car". Watch the resulting confusion. We have a need to talk about object values, and we also have a need to talk about object variables. If no one ever points out the difference for us, no wonder we get confused. - But this means that you should be extremely cautious when it comes to adding state changing methods to your class. Yes. 6. Conclusions We have seen that it is neccessary to separate the notion of variable and the notion of value. By doing that we see that: **** Subtype values can be used in all contexts where **** values of a supertype can be used. By using only functions in our classes (const declared) we are safe in passing an object of a subtype to a routine expecting an object of a supertype. Whenever the routine has to use a procedure in the supertype class, we are in trouble subtype-wise. This separation will also lead to clearer designs and clearer programs, simply by decreasing the amount of obscurity caused by confusion in this area. Of course, none of this applies to squares and rectangles :-) :-) --- Copyright 1996 Jonas Mölsä. This article may be freely distributed in its entirety for non-commercial purposes. ------------------------------------------------------------------------------ Jonas Mölsä, jbm@nada.kth.se |Jonas Molsa, jbm@nada.kth.se NADA, KTH |Dept. of Num. Analysis and Computing Science 100 44 Stockholm |Royal Institute of Technology |100 44 Stockholm |Sweden