Newsgroups: comp.lang.c++,comp.object,comp.theory,comp.lang.smalltalk
Path: cantaloupe.srv.cs.cmu.edu!rochester!cornellcs!newsstand.cit.cornell.edu!newstand.syr.edu!news.maxwell.syr.edu!news.bc.net!torn!kwon!watserv3.uwaterloo.ca!undergrad.math.uwaterloo.ca!eadengle
From: eadengle@dino.uwaterloo.ca (Ed "Cynwrig" Dengler)
Subject: Re: Opinions on Ellipse-Circle dilemma?
Sender: news@undergrad.math.uwaterloo.ca (news spool owner)
Message-ID: <E5Jzvv.LE3@undergrad.math.uwaterloo.ca>
Date: Thu, 13 Feb 1997 18:11:55 GMT
References: <5d4fmf$7gk@bagan.srce.hr> <E5B2HH.8AH@ecf.toronto.edu> <E5EMwz.74K@undergrad.math.uwaterloo.ca> <32ffb03c.0@news3.paonline.com>
Nntp-Posting-Host: dino.uwaterloo.ca
Organization: University of Waterloo
Lines: 125
Xref: glinda.oz.cs.cmu.edu comp.lang.c++:247737 comp.object:61121 comp.theory:17945 comp.lang.smalltalk:51573

Greetings!

In article <32ffb03c.0@news3.paonline.com>,  <mfinney@lynchburg.net> wrote:
>In <E5EMwz.74K@undergrad.math.uwaterloo.ca>, eadengle@hoporoo.uwaterloo.ca (Ed "Cynwrig" Dengler) writes:
>
>>CLASS Circle: SUBCLASS Ellipse
>
>
>>A: Circle
>>B: Ellipse
>
>>A.Create
>>B := A
>>B.setAxesLength( 5, 10 )
>
>>Hmmmm, doesn't work too well does it?
>
>>The problem of matching subtypes is that you also match interfaces, which means
>>that any subclass better support what its superclass can be told to do.
>
>The problem is not interfaces, but rather a "first-order" definition.
>The Liskov substitution principle only holds when the class can be
>defined without using second-order predicates (which contain self
>reference).  A second order example which works quite well is...
>
>
>class Ellipse
>   {
>   private:
>      double xAxis;
>      double yAxis;
>   public:
>      virtual bool isValidAxesRatio(double x, double y) const
>         {
>         return true;
>         }
>      void setAxes(double x, double y)
>         {
>         if (not isValidAxesRatio(x, y))
>            throw "Ellipse precondition exception";
>         xAxis = x;
>         yAxis = y;
>         }
>   };
>
>class Circle : Ellipse
>   {
>   virtual bool isValidAxesRatio(double x, double y) const
>      {
>      return x == y;
>      }
>   };
>
>class Oval : Ellipse
>   {
>   virtual bool isValidAxesRatio(double x, double y) const
>      {
>      return x != y;
>      }
>   };
>

Ugghhh!! While you are correct in that this structure may hold properly,
note that you _have_ changed the semantics of an ellipse (an ellipse can
only change both axes if it meets some criteria which is modifiable).  Let us
examine this from the perspective of real application programming, where
the classes might be written by different people.  You mean to tell me
that some library implementor must predict all possible future uses of
a particular class?  If I were to implement Ellipse in total isolation,
then it is highly unlikely that I would develop the class as you specify.
If you were then to come along and try to develope a Circle class from my
Ellipse class, you would be out of luck as I would _not_ have provided
this isValidAxesRatio method (I mean, why should I?? an _ellipse_ can
allows have its axes changed independently!).

>This solution works because the setAxes() method has a precondition
>which only permits it to be called for valid x and y ratios.  That
>precondition can be separately checked by calling isValidAxesRatio();.

Yes, but this semantic condition is required at the development stage
of the Ellipse class, something that is unlikely to occur.

>Essentially, the set of valid ratios is part of the class predicate and
>the additional constraints placed on Circle do not violate any of the
>constraints on Ellipse.  Membership in that set is checked by calling
>the isValidAxesRatio() method and the precondition on setAxes() ensure
>that a Circle will always be round.  But, notice that the similar
>Oval class cannot inherit from Circle because it would violate the
>constraints on Circle.  It can, however, inherit from Ellipse as a
>sibling of Circle.
>
>Now, if you have an Ellipse instance in your code which does not
>violate the constraints on circle, then a Circle instance can be
>substituted.  So, second order substition works correctly.  However,
>the class predicate is second order and not first order, and so you
>cannot substitute a Circle for an arbitrary Ellipse.
>
>Some people may be uncomfortable with class designs which use
>second order references.  However, they are essential in many cases.
>Eiffel directly supports a limited second order constraint since it
>allows covariant method parameters.  Also, parallel class heirarchies
>are another example where second order references come into play.
>
>The only thing about this example which is unusual is that the
>"obvious" mathematical intuition and the "obvious" programming
>intuition conflict.  I suggest that they are not in conflict when you
>consider that the appropriate class definition contains a second
>order predicate.
>
>Liskov's Substitution Principle is widely quoted, but is limited to
>first order constraints.  Where second order constraints are present,
>it breaks down as stated.  It can, of course, be reformulated in a
>second order form.

But only if all semantic considerations are known in advance (now, if
some language would support after the fact additions to class libraries,
this might change my opinion).

>
>
>Michael Lee Finney
>

Ed

