On 8/25/06, Daniel Hulme <[EMAIL PROTECTED]> wrote:
That's because you're used to one way of thinking about class
inheritance: that the subclass can do everything that the superclass can
do, and more. In this scheme, you might have a Square class, with a
field representing its corner and another giving its side length. Then,
you could build on this to have Rectangle as a subclass, which adds an
extra side length, and extra accessors for it. This is a really bad way
of making your subclasses work, but your Rectangle has all the fields
and methods of your Square, and some extra ones.

This is the well-known Circle/Ellipse problem, and it relates to the
theory of value types.

We'll first take the traditional conception that a Square is a
Rectangle, the opposite of your viewpoint.  This is based on the
geometrical definitions of these shapes.  This makes sense from a
usage standpoint:

   sub rect_perimiter(Rectangle $x) {
       2*$x.width + 2*$x.height;
   }
   sub square_perimiter(Square $x) {
       4*$x.width;
   }

It makes sense to pass a Square to rect_perimiter, but not to pass a
Rectangle to square_perimiter.  That is one indication that Square is
a subtype of Rectangle.

However, say that Rectangle had set_height() and set_width() methods.
You cannot set the height and width of a square independently, so a
function like this:

   sub make_dims(Rectangle $r, $w, $h) {
       $r.set_width($w);
       $r.set_height($h);
       POST { $r.area == $w * $h }
   }

Must fail if $w and $h are not equal (and depending on Square's
implementation, might fail if they are equal).  You could say that the
failure is on the implementation side: we couldn't implement set_width
and set_height appropriately.

So clearly a Square is not a Rectangle.

Let's look at the other way around (your viewpoint):  a Rectangle is a
Square.  This makes sense from an implementation point of view, as you
point out.  We just take Square's methods and add a couple of
capabilites.

However:

   sub area(Square $x) {
       $x.width ** 2;
   }

Fails (by returning the wrong thing, worse than dying) if you pass it
a Rectangle.  So it failed from the usage point of view.  You could
say "that should be a method", but then you should say that everything
that uses a square should be a method of square, because we must be
able to make assumptions about the  behavior of classes.  If you say
that my sub makes an assumption that it shouldn't, realize that the
only thing it is assuming is that a Square is a geometrical square.
If I can't make that assumption, then Square is not a very good name
for that class.

So clearly a Rectangle is not a Square.

One way failed on the implementation side, the other on the usage
side.  I'd argue that the "Rectangle is a Square" view's validity can
only be argued from an implementation laziness point of view (which is
okay, but it must also be balanced with other issues).

The "Square is a Rectangle" point of view only works if they are value
types: if you are not allowed to modify anything.  In that
circumstance, it works very cleanly[1].  But if you are allowed to
modify things, then the two classes must be siblings, not parent-child
related.

Luke

Reply via email to