Object Oriented Hierarchies: Squares and Rectangles

Author: Jonas Mölsä
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

Email me , or tweet @redblobgames, or comment: