[comp.lang.eiffel] Inheritance and Information Hiding

hoelzle@Neon.Stanford.EDU (Urs Hoelzle) (07/27/90)

While reading Meyer's OOSC I stumbled over this (section 11.5,
"Inheritance and Information Hiding"):

   "The Eiffel answer is quite simple [..] the two mechanisms are
    completely orthogonal."

I can see how you can claim this in a dynamically-typed language, but
I don't understand what `Information Hiding (IH)' means in a
statically typed language like Eiffel if the two mechanisms
(inheritance and IH) are indeed orthogonal.  IMHO you can no longer
(statically, i.e. at compile time) enforce IH in this case.

Consider this:

    class A  exports feature f
    class B  (a subclass of A) does not export f

That is, if a is of type A and B of type B, a.f is legal, but b.f is
not.  Now consider a code sequence such as

    a := <some expression yielding a value of type A *or* B>;
    a.f

The invocation of feature f is *only* legal if the object pointed to
by a is actually an instance of class A.  If it were an instance of
class B, we would illegally invoke a "hidden" feature, violating the
contract metaphor and the assertion system.

It is certainly possible to check for this at run time but not at
compile time.  That is, given the assumption that inheritance and IH
are orthogonal, IH semantics cannot be enforced statically.  In fact,
Eiffel is not type-safe if we consider IH violations to be type errors.

Am I missing something or is this indeed a problem?

-Urs

bertrand@eiffel.UUCP (Bertrand Meyer) (07/27/90)

From <1990Jul26.221405.23439@Neon.Stanford.EDU>
by hoelzle@Neon.Stanford.EDU (Urs Hoelzle):

> Am I missing something or is this indeed a problem?

Yes.

I mean: yes, there is a problem and yes, you are missing
something, if only previous discussions on this newsgroup.

The issue was discussed at length on at least two occasions
(July 1989 and last March). Anyone who has access to
archives of the newsgroup can go to my March posting
<265@eiffel.UUCP>, which includes references to many
earlier messages by various people.
-- 
-- Bertrand Meyer
bertrand@eiffel.com

carroll@udel.edu (Mark Carroll <MC>) (07/27/90)

In article <1990Jul26.221405.23439@Neon.Stanford.EDU> hoelzle@Neon.Stanford.EDU (Urs Hoelzle) writes:
]While reading Meyer's OOSC I stumbled over this (section 11.5,
]"Inheritance and Information Hiding"):
]
]   "The Eiffel answer is quite simple [..] the two mechanisms are
]    completely orthogonal."
]
]I can see how you can claim this in a dynamically-typed language, but
]I don't understand what `Information Hiding (IH)' means in a
]statically typed language like Eiffel if the two mechanisms
](inheritance and IH) are indeed orthogonal.  IMHO you can no longer
](statically, i.e. at compile time) enforce IH in this case.
]
]Consider this:
]
]    class A  exports feature f
]    class B  (a subclass of A) does not export f
]
]That is, if a is of type A and B of type B, a.f is legal, but b.f is
]not.  Now consider a code sequence such as
]
]    a := <some expression yielding a value of type A *or* B>;
]    a.f
]
]The invocation of feature f is *only* legal if the object pointed to
]by a is actually an instance of class A.  If it were an instance of
]class B, we would illegally invoke a "hidden" feature, violating the
]contract metaphor and the assertion system.
]
]It is certainly possible to check for this at run time but not at
]compile time.  That is, given the assumption that inheritance and IH
]are orthogonal, IH semantics cannot be enforced statically.  In fact,
]Eiffel is not type-safe if we consider IH violations to be type errors.
]
]Am I missing something or is this indeed a problem?
]

I think that you are missing something. It IS possible to check this
statically. 

If you define a taglist for each feature in the system, consisting
of the list of features called for each argument used in the proceedure,
making this a recursive process (that is, for routine A, include 
the features used directly in A, and the features used by each routine
called by A), you have a total list of features used for each argument
to the routine. When you make use of any instancye of class A, you
consult this taglist, to insure that every feature used in exported
by the class. It's certainly not easy or fast, but it is possible. (I
do not have ANY idea if this is what Eiffel does!)

]-Urs

	<MC>

--
|Mark Craig Carroll: <MC>  |"We the people want it straight for a change;
|Soon-to-be Grad Student at| cos we the people are getting tired of your games;
|University of Delaware    | If you insult us with cheap propaganda; 
|carroll@dewey.udel.edu    | We'll elect a precedent to a state of mind" -Fish

kaplan@scooby.cs.umass.edu (Alan Kaplan) (07/27/90)

In article <382@eiffel.UUCP> bertrand@eiffel.UUCP (Bertrand Meyer) writes:

   ...

   The issue was discussed at length on at least two occasions
   (July 1989 and last March). Anyone who has access to
   archives of the newsgroup can go to my March posting
   <265@eiffel.UUCP>, which includes references to many
   earlier messages by various people.


Can you point out how/where you can access these archive?  Thanks!

                    
                             Regards,

                             Alan 

                             kaplan@cs.umass.edu
--

                             Regards,

                             Alan 

                             kaplan@cs.umass.edu

hoelzle@Neon.Stanford.EDU (Urs Hoelzle) (07/27/90)

bertrand@eiffel.UUCP (Bertrand Meyer) writes:

>From <1990Jul26.221405.23439@Neon.Stanford.EDU>
>by hoelzle@Neon.Stanford.EDU (Urs Hoelzle):

>> Am I missing something or is this indeed a problem?

>Yes.

>I mean: yes, there is a problem and yes, you are missing
>something, if only previous discussions on this newsgroup.

>The issue was discussed at length on at least two occasions
>(July 1989 and last March). Anyone who has access to
>archives of the newsgroup can go to my March posting
><265@eiffel.UUCP>, which includes references to many
>earlier messages by various people.

Could someone please mail me these articles?  

aTdHvAaNnKcSe,

-Urs

bertrand@eiffel.UUCP (Bertrand Meyer) (07/30/90)

In <1990Jul27.163928.5886@Neon.Stanford.EDU>, hoelzle@Neon.Stanford.EDU
(Urs Hoelzle) asks

> Could someone please mail me these articles [about problems in
> Eiffel type checking]?  

In <KAPLAN.90Jul27123145@scooby.cs.umass.edu> Alan Kaplan asks
whether there is any publicly available archive of comp.lang.eiffel.

Although Interactive Software Engineering has a complete (we think)
record of the newsgroup, we are not able to provide a publicly
available archive (through anonymous FTP or something of the sort),
and most likely will not be in the near future.

This is regrettable since there have been many insightful postings
on topics that keep coming up in queries made by people who become
interested in Eiffel and related issues. It is hard for us to keep on
mailing individual copies of old postings to many people. I have been
thinking of preparing a printed ``comp.lang.eiffel's greatest hit''
collection, but this is a somewhat lower priority project, which
also involves asking posters' permission and other hassles.

If any university or other institution is equipped and willing to
support a publicly available archive of future postings,
we will be glad to provide it with a full copy of our records
of the newsgroup, to make sure that the archive is complete. 

In the meantime, since questions about the type system come up
so frequently, and my July 1989 article, not publicly available
elsewhere, is the least unsatisfactory answer I can give,
I am reposting it, together with a companion article on ``Eiffel
types''. See next three postings. 

-- Bertrand Meyer
bertrand@eiffel.com

chrisv@runx.oz.au (Chris Velevitch) (01/24/91)

I disagree with allowing a class to access inherited features that are
not exported. It does not make sense that secret features are known to
heir of a class. If a feature is not publicly known, then how can you
know about the feature to use in the descendent class.

Eiffel allows a class to be designed so it has no interface, which can
then be used in descendent classes. What use is a black box in which you
cannot put anything in or take anything out.

When designing a class, a designer makes the interface public, not its
implementation. The user of a class buys services from the class based
solely from its publically known behaviour and features. You can only
inherit features that are known. I consider inheritance as a form of
buying services.

-- 
Chris Velevitch
RUNX Unix Timeshare    | Internet: chrisv@runxtsa.runx.oz.au
Sydney NSW, Australia. |     UUCP: uunet!runxtsa.runx.oz.au!chrisv

barmar@think.com (Barry Margolin) (01/25/91)

In article <1991Jan23.224203.3206@runx.oz.au> chrisv@runx.oz.au (Chris Velevitch) writes:
>I disagree with allowing a class to access inherited features that are
>not exported. It does not make sense that secret features are known to
>heir of a class. If a feature is not publicly known, then how can you
>know about the feature to use in the descendent class.

I like C++'s solution to this, the private/protected/public distinction.
The private members of a class are only visible within the class, internal
to the "black box".  Protected members are visible to derived classes;
frequently class derivation is used to extend behavior, in which case it is
reasonable to allow a more intimate relationship between the base class and
the subclasses, and protected members can be used for this.  And public
members are fully exported.

The applicability of information hiding ideas can depend heavily on the
design of the OO language.  For instance, it's hard to provide this could
be done in CLOS, because there is no notion of methods "belonging" to a
particular class, due to its support of generic function dispatch based on
the types of multiple arguments.  I suppose there could be rules specifying
that WITH-SLOTS and WITH-ACCESSORS may only be used on specialized
parameters, and may only specify slots visible within the class named in
the parameter specializer.  This would support information hiding with
regard to data, but it's not clear how to extend it to procedures.

--
Barry Margolin, Thinking Machines Corp.

barmar@think.com
{uunet,harvard}!think!barmar

tynor@hydra.gatech.edu (Steve Tynor) (01/26/91)

>In article <1991Jan23.224203.3206@runx.oz.au> chrisv@runx.oz.au (Chris Velevitch) writes:
>>I disagree with allowing a class to access inherited features that are
>>not exported. It does not make sense that secret features are known to
>>heir of a class. If a feature is not publicly known, then how can you
>>know about the feature to use in the descendent class.
>
>I like C++'s solution to this, the private/protected/public distinction.
>The private members of a class are only visible within the class, internal
>to the "black box".  Protected members are visible to derived classes;
>frequently class derivation is used to extend behavior, in which case it is
>reasonable to allow a more intimate relationship between the base class and
>the subclasses, and protected members can be used for this.  And public
>members are fully exported.
 
In practice, I've found C++ `private's to be overly restrictive and in
violation of the rule "it is not the buisiness for a class to decide how it may
be extended in the future" (paraphrased from OOSC). It's another example of a
C++ feature that actually reduces the reusability of C++ classes (non-virtual
member functions being the other obvious one).

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
C++: Just say no.
                     
    Steve Tynor
    Georgia Tech Research Institute
    Artificial Intelligence Branch
    tynor@prism.gatech.edu

bertrand@eiffel.UUCP (Bertrand Meyer) (01/26/91)

From <1991Jan23.224203.3206@runx.oz.au> by chrisv@runx.oz.au (Chris Velevitch):
> I disagree with allowing a class to access inherited features that are
> not exported. It does not make sense that secret features are known to
> heir of a class. If a feature is not publicly known, then how can you
> know about the feature to use in the descendent class.
> 
> Eiffel allows a class to be designed so it has no interface, which can
> then be used in descendent classes. What use is a black box in which you
> cannot put anything in or take anything out.

This has come up before, of course.

There is little to add to Steve Tynor's response. Let me, however,
reproduce an extract from a paper called ``Static Typing in Eiffel'',
of which a preliminary version was posted about two years ago.
The current version is part of a book called ``An Eiffel Collection'',
which is a collection of Eiffel-related articles, published by
Interactive.

The text is an ASCII-equivalent of something meant for typesetting,
with heavy use of italics, boldface and some graphics. The general
ideas, however, should survive e-mail translation.

- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
            Extracted from ``Static Typing for Eiffel''

In: An Eiffel Collection
Published by Interactive Software Engineering, 1991.
Reference TR-EI-20/EC.
Copyright B. Meyer, 1991
- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

4.1 Rationale for the export rule


Consider first the export-inheritance rule, which often surprises
newcomers   as   an  apparent  violation  of  information  hiding
principles. How can a descendant override export restrictions  of
an ancestor, or hide what the ancestor exported?

     To obtain a better perspective on this rule it is useful  to
examine  its application to what may be the archetypal example of
inheritance. Assume PARENT is a class POLYGON and HEIR is a class
RECTANGLE.  The  inheritance  relation  in this case is clear and
natural.

     It is not absurd, however, to assume that class POLYGON  has
a procedure
     add_vertex (new: POINT) is
               -- Insert new vertex new at current cursor position
          do
               ...
          ensure
               nb_vertices = old nb_vertices + 1
          end -- add_vertex

     For general polygons, this procedure may make sense.  It  is
not,  however,  applicable  to  rectangles, whose class invariant
should contain the clause
     nb_vertices = 4

     The Eiffel solution is to ensure that class  RECTANGLE  does
not  export  procedure  add_vertex. This is the kind of case that
led to situations such as [REFERENCE TO EARLIER EXAMPLE OF
ERRONEOUS FEATURE CALL OF THE FORM x.f],
with f being add_vertex.

|- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- ---|
|                                                                    |
|                                                                    |
|           (Sorry, no graphics available for e-mail)                |
|                                                                    |
|(Please draw a figure with a bubble labeled POLYGON at the center   |
|top; below it,  bubbles labeled FIXED_POLYGON and VARIABLE_POLYGON, |
|with a single arrow from each of these bubbles to the top one;      |
|below FIXED_POLYGON, a bubble labeled RECTANGLE, with a bubble      |
|to FIXED_RECTANGLE.)                                                |
|                                                                    |
|                                                                    |
|               Figure 1: Alternative inheritance structures         |
|                                                                    |
- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -|


     Three policies are possible in the face of such cases.

     + Policy  1  considers  that  this  use  of  inheritance  is
       inadequate:  RECTANGLE is not a subtype of POLYGON because
       not all operations applicable to the latter are applicable
       to  the  former.  According  to  this  policy,  the proper
       solution (see figure  1)  is  to  consider  two  heirs  to
       POLYGON:  FIXED_POLYGON  and  VARIABLE_POLYGON.  Procedure
       add_vertex is a feature of VARIABLE_POLYGON only,  whereas
       RECTANGLE inherits from FIXED_POLYGON. This way the Eiffel
       rule can be changed to require  that  every  class  should
       export  all features exported by its ancestors. (Even with
       this policy, however, there is no  reason  to  prohibit  a
       class   from  exporting  a  feature  hidden  by  a  proper
       ancestor. This technique is  commonly  used  in  practical
       Eiffel  programming  and,  as  discussed above, entails no
       danger of type failure.)

     + Policy 2 considers that inheritance is  used  properly  in
       this  case, but only for half of its capacity: as a module
       enrichment mechanism, not as  a  subtyping  facility.  The
       definition of class RECTANGLE should be permitted, but not
       polymorphic assignments of the form p := r (with p of type
       POLYGON  and  r  of type RECTANGLE). With such a policy
       the conformance rules
       governing such assignments, as outlined in section  11.3.1
       of  ``Object-Oriented  Software  Construction'' (Prentice-
       Hall, 1988) and given fully in ``Eiffel: The Language''
       (Interactive Software Engineering, 1989, and Prentice-Hall,
       1991), would have to  be updated to exclude such
       cases. If polymorphic assignments  are  needed,  then  the
       inheritance  structure  should  be  redesigned as shown in
       figure 1.

     + Policy 3 is more liberal: it permits the above polymorphic
       assignments as long as it can be ascertained statically at
       reasonable cost, for any  Eiffel  system  involving  these
       classes,  that no type failure may result; in other words,
       that no add_vertex operation may ever be applied to  p  or
       an  entity  that may become associated with p. This is the
       policy applied in Eiffel, for which the exact  rules  will
       be given below.

     What speaks in favor of policies 1 and 2 is  that  they  are
extremely  easy to implement for the compiler writer. Restricting
exports in descendant classes (policy 1) is trivial;  restricting
polymorphic  aliasing  (policy  2)  is almost as immediate. These
solutions  would  also  have  the  advantage  of   quieting   the
theoreticians and removing the fears of prospective users.

     In short, all obvious arguments seemed to push the designers
of  Eiffel  towards  policy  1  or 2: convenience (since the same
group is also  in  charge  of  an  implementation)  and  ease  of
convincing  future  users.  So it takes some dose of fortitude to
choose policy 3 and stick to it.  Why should this be  the  Eiffel
policy?

     The reasons can only  be  understood  by  going  beyond  the
purely  theoretical  discussions  and considering the practice of
object-oriented software construction.

     Policies 1 and 2 assume that programming is for  gods.  Gods
never  make  any  mistake.  Those among them who aside from their
other business indulge  in  the  heavenly  pleasures  of  object-
oriented  programming  always  get  their  inheritance structures
right the first time around.

     In today's job market, however, most employers  must  resort
to  hiring  programmers  who  are  only demigods, or even in some
cases (although they will deny it) mere mortals.  Then we have to
accept  the  need  to  work  with inheritance structures that may
already in be place and have been designed with less-than-perfect
foresight.

     Assume for example that POLYGON has  already  been  designed
and  has  descendants such as CONVEX_POLYGON and CONCAVE_POLYGON.
In other words, the existing inheritance structure does not  take
into  account  the  difference between ``variable'' and ``fixed''
polygons. Then a new programmer comes  in  and  has  a  need  for
RECTANGLE;  writing  it  as  an heir to POLYGON seems the obvious
solution.

     Policy 1 precludes doing this without first reorganizing the
entire  inheritance structure. This is unrealistic in a practical
development environment.  True, we should  accept  the  need  for
regular   improvements   of  inheritance  structures,  reflecting
improved understanding of software artefacts. But as  anyone  who
has  managed  a  class  library in an industrial environment will
appreciate,  such  evolution  should  be  carefully  planned  and
properly  controlled.  One cannot accept a situation in which new
but legitimate uses of a class require constant  changes  in  the
design of existing libraries.

     Policy 2 is  only  marginally  better.  It  does  allow  the
programmer to define RECTANGLE as an heir to POLYGON but prevents
him from using polymorphism in this case; it is not possible, for
example, to define a data structure such as

     polygon_stack: STACK [POLYGON]

and  push  a  RECTANGLE  object  onto  polygon_stack.   This   is
particularly  frustrating  in an application that never even uses
add_vertex.  Again,  the  only  solution  is  to   redesign   the
inheritance structure.

     Policies 1 and 2 are overly  inflexible.  In  contrast,  the
idea   of  policy  3  is  to  avoid  bothering  programmers  with
unnecessary restrictions when their  use  of  inheritance  cannot
possibly  lead  to  any  type failure.  The criterion to apply is
obvious in the example  given:  the  checker  should  reject  any
software system that contains both of the operations

     + p := r

     + p.add_vertex (...)

     Because  checkers  cannot  realistically  carry   out   flow
analysis,  it  is not necessary to check whether or not these two
instructions can be executed on the same control flow  path;  the
mere presence of both in the same system is reason enough for the
checker to reject that system. In other words, we accept  without
regret  that  the  following  sequence (with n an integer entity)
will be rejected even though it cannot possibly lead  to  a  type
failure:

     if n >= 0 then p := r end
     if n < 0 then p.add_vertex (...) end

     The reason for this was  discussed  in  [AN EARLIER] section
[OF THE PAPER]: checkers should  only  be  bound to perform
``reasonable'' controls. For a
checker to recognize that the above is safe requires control flow
analysis,  whose cost, if it is at all possible, is not justified
by the benefit.  Using the terminology of [AN EARLIER] section,
we  are  past the point of diminishing returns.

     As noted [EARLIER], type checking is always  a  pessimistic  strategy;
the  only  question  is  what  degree of pessimism is acceptable.
Rejecting extracts of the above form appears  acceptable  because
it  is  hard  to  conceive of them being used in useful programs.
This is not true, however, of the much stronger pessimism implied
by  policies  1 and 2, which leads to rejection of programs that,
for the reasons discussed above, are useful and even needed.

     How policy 3 can be implemented at reasonable cost  will  be
discussed [IN LATER SECTIONS].


4.2 Further reflections on inheritance


The argument made above for  orthogonal  export  and  inheritance
mechanisms  was  that  inheritance  structures may be temporarily
imperfect. The  implied  consequence  is  that  eventually  these
structures  will  be  perfected;  then  policies 1 and 2 could be
implemented if we  were  willing  to  implement  tougher  library
management  procedures  (forcing  restructuring when appropriate,
and requiring new heir designs to wait in the meantime).

     Unfortunately, even such an approach may not be  viable.  In
practice we must probably accept that some inheritance structures
will always be imperfect.

     Inheritance is the application to  software  engineering  of
the    most    fundamental    of   all   scientific   techniques:
classification.  The modern forms of the natural sciences,  since
Linnaeus, and of mathematics, at least since Bourbaki, were borne
out of systematic efforts  to  classify  natural  and  artificial
objects;   object-oriented  programming  attempts  the  same  for
software objects.

     Classification is humanity's attempt to bring a semblance of
order to the description of an otherwise rather chaotic world. As
anyone who has ever tried to  use  a  botany  book  to  recognize
actual   flowers   knows,  classifications  never  quite  achieve
perfection. Close as one can get to a fully satisfactory  system,
a few exceptions and special examples will remain.

     Moving from  botany  to  zoology,  the  cliche'  example  of
ostriches  and  flying  illustrates these problems well. In class
BIRD, it seems appropriate to include and export a  feature  ``fly''.
But  a  descendant  class such as STRUTHIO (the name of the genus
that includes ostriches) should not export ``fly''.  This  is  really
the  Eiffel  form  of selective inheritance, or rejecting part of
your heritage: the rejected feature is  still  there  internally,
but  not  visible  by  your clients. (The discussion of selective
inheritance  in  section  10.5.3  of ``Object-Oriented Software
Construction'' is too restrictive in this respect: it
seems to reject any form of selective  inheritance,  even  though
Eiffel has always supported the form discussed here.)

     In a case such as this one there does not seem to be a  much
better  solution.  Any other classification of birds, for example
into flying and non-flying ones,  would  miss  key  criteria  for
distinguishing  between  various  classes  of  birds,  and  would
undoubtedly cause bigger problems  than  the  original  solution.
Multiple  inheritance  from  a  class FLYING_THING would not help
much.

     So far, only a minority  of  Eiffel  users  have  admittedly
reported  ostrich-oriented  programming  as  their  major area of
technical  interest.   But  the  need  to  deal  with   imperfect
inheritance  structures  seems  universal.   Of course, we should
strive to make  these  structures  as  complete  and  regular  as
possible.  But  we must also accept that unexpected special cases
and exceptions may always occur. When they do and the  programmer
is   only   performing   safe   manipulations,   the  programming
environment should help him, not put undue  restrictions  in  his
way.


- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
            End of extract
- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

-- Bertrand Meyer
bertrand@eiffel.com

paj@mrcu (Paul Johnson) (01/28/91)

>In article <1991Jan23.224203.3206@runx.oz.au> chrisv@runx.oz.au (Chris Velevitch) writes:
>>I disagree with allowing a class to access inherited features that are
>>not exported. It does not make sense that secret features are known to
>>heir of a class. If a feature is not publicly known, then how can you
>>know about the feature to use in the descendent class.
>
>I like C++'s solution to this, the private/protected/public distinction.

I think the idea is that a child should be able to see all of its
parents attributes so that the implementation is not constrained by
the decisions of the parents' implementors.  If someone decides to
construct a class which inherits from a single parent simply to export
more attributes then that is their buisness.  They have designed and
built a new class.  If the interface is bad, or the invarients no
longer hold or some similarly idiotic thing then that is the
programmer's fault for producing a bad design.  Eiffel is not a
bondage and discipline language, and like any good language it will
give the programmer plenty of rope.  If he hangs himself, well he
should have taken more care and heeded the safty warnings.

The big advantage of Eiffel over C++ is that thanks to conceptual
simplicity, all the dangerous things in Eiffel can be clearly
signposted.  C++ forces you to take the blade-guards off in order to
use it.  The private/protected/public system is just sugar and I
cannot see how it helps anyone.  I can see how it would get in their
way though.

Paul.


-- 
Paul Johnson                               UUCP: <world>!mcvax!ukc!gec-mrc!paj
--------------------------------!-------------------------|-------------------
GEC-Marconi Research is not 	| Telex: 995016 GECRES G  | Tel: +44 245 73331
responsible for my opinions.	| Inet: paj@uk.co.gec-mrc | Fax: +44 245 75244

chip@tct.uucp (Chip Salzenberg) (01/28/91)

According to rick@tetrauk.UUCP (Rick Jones):
>In article <1991Jan24.214652.18515@Think.COM> barmar@think.com (Barry Margolin) writes:
>>I like C++'s solution to this, the private/protected/public distinction.
>>The private members of a class are only visible within the class, internal
>>to the "black box".  Protected members are visible to derived classes ...
>>And public members are fully exported.
>
>The problem I see with this approach is that the designer of a class has to
>anticipate every possible way in which his class may be used by another
>programmer.

I'm sorry, but I don't believe that any method of code reuse --
whether inheritance, delegation or "editor block-copy subroutines" --
will ever be able to guarantee 100% reuse *without modification*.

The C++ public/protected/private specifications are a concise way for
the programmer of a class to express his expectations as to future
derivation from the given class.

If a second programmer thinks that she needs to get at a private
member of such a class, that is a hint that the base class's author
didn't anticipate the second programmer's intended use of the class.
It will imply to a good programmer that either (1) the derivation in
question is a Bad Idea, or (2) the base class requires some adjustment
before the derivation will work.

I don't see how eliminating the "private" keyword would fix this basic
difficulty with deriving from another programmer's classes.
-- 
Chip Salzenberg at Teltronics/TCT     <chip@tct.uucp>, <uunet!pdn!tct!chip>
       "If Usenet exists, then what is its mailing address?"  -- me
             "c/o The Daily Planet, Metropolis."  -- Jeff Daiell

chip@tct.uucp (Chip Salzenberg) (01/28/91)

According to tynor@hydra.gatech.edu (Steve Tynor):
>In practice, I've found C++ `private's to be overly restrictive and in
>violation of the rule "it is not the buisiness for a class to decide how
>it may be extended in the future" (paraphrased from OOSC).

An interesting "rule" -- one with which I happen to disagree.

In any case, language features don't stand up and say, "Use me!"  Who
can say that there will never be good reason to make members private?
Not I -- even though I use "protected" five times more often than I
use "private".

>It's another example of a C++ feature that actually reduces the reusability
>of C++ classes (non-virtual member functions being the other obvious one).

Virtual functions are available when appropriate; but I'm just as glad
that I need not pay the performance penalty when I don't need them.
-- 
Chip Salzenberg at Teltronics/TCT     <chip@tct.uucp>, <uunet!pdn!tct!chip>
       "If Usenet exists, then what is its mailing address?"  -- me
             "c/o The Daily Planet, Metropolis."  -- Jeff Daiell

chrisv@runx.oz.au (Chris Velevitch) (01/30/91)

In article <1991Jan24.214652.18515@Think.COM> barmar@think.com (Barry Margolin) writes:
>In article <1991Jan23.224203.3206@runx.oz.au> chrisv@runx.oz.au (Chris Velevitch) writes:
>>I disagree with allowing a class to access inherited features that are
>>not exported. It does not make sense that secret features are known to
>>heir of a class. If a feature is not publicly known, then how can you
>>know about the feature to use in the descendent class.
>
>I like C++'s solution to this, the private/protected/public distinction.
>The private members of a class are only visible within the class, internal
>to the "black box".  Protected members are visible to derived classes;
>frequently class derivation is used to extend behavior, in which case it is
>reasonable to allow a more intimate relationship between the base class and
>the subclasses, and protected members can be used for this.  And public
>members are fully exported.

I can't see the real need for the protected members, because you are
publicly announcing the existence of the members. You might as well
just make them public. The class specifications remainings the same. 
I believe that by making features protected, you are making a supposition
on how the class will be used. You may find that once having made it known
the feature exists, a user will find a case in which they only want to buy
the feature and not inherit it.

-- 
Chris Velevitch
RUNX Unix Timeshare    | Internet: chrisv@runxtsa.runx.oz.au
Sydney NSW, Australia. |     UUCP: uunet!runxtsa.runx.oz.au!chrisv

jbuck@galileo.berkeley.edu (Joe Buck) (01/31/91)

In article <808@puck.mrcu>, paj@mrcu (Paul Johnson) writes:
|> The big advantage of Eiffel over C++ is that thanks to conceptual
|> simplicity, all the dangerous things in Eiffel can be clearly
|> signposted.  C++ forces you to take the blade-guards off in order to
|> use it.  The private/protected/public system is just sugar and I
|> cannot see how it helps anyone.  I can see how it would get in their
|> way though.

Read Grady Booch's book and he'll tell you why.  The private/protected/
public feature is one of his favorite things about the language, and
he gives good arguments.  The problem is that you need to know how to
use it.

--
Joe Buck
jbuck@galileo.berkeley.edu	 {uunet,ucbvax}!galileo.berkeley.edu!jbuck	

bertrand@eiffel.UUCP (Bertrand Meyer) (01/31/91)

	Anyone who wants to argue that the Eiffel policy is not the
appropriate one is going to have to leave pious generalities
and offer serious technical arguments.

	Will anyone take up the simplest example (discussed in my
posting <485@eiffel.UUCP>): a class POLYGON exists and has
a procedure ``add_vertex'', which it exports.
You want to add a class RECTANGLE. What do you do?

1. Decide not to use inheritance. No comment.

2. Redo the existing inheritance hierarchy. Of course,
even assuming you are permitted to do it, this will only
save you temporarily: someone else, or you a while later,
will want to perform new changes based on criteria other
than fixed versus variable number of vertices:
for example regular versus irregular, etc. You'll soon
have a total mess if you are trying to keep up with various
conflicting classifications.

3. Accept that RECTANGLE inherits from POLYGON and does not export
``add_vertex''. (This procedure violates the invariant of class
RECTANGLE anyway, so the consistency rules forbid it to be exported,
but of course non-exported routines do not have to maintain the
invariant.) To avoid burdening the compiler writer,
call this ``implementation inheritance'' and disallow
polymorphic assignments of the form p := r where p is of type
POLYGON and r of type RECTANGLE.

4. Accept that RECTANGLE inherits from POLYGON and does not export 
``add_vertex''. Permit polymorphic assignments of the above form;
but have the type checker reject as invalid any system which would
contain both such an assignment and a call p.add_vertex (...), since
it may lead to a run-time inconsistency. Do not introduce
any distinction between ``implementation inheritance'' and any other
kind.

Based on not inconsiderable experience with the design of O-O systems
and class hierarchies, I believe that solution 4 is the only reasonable
one. I would be interested to see any technical counter-argument,
and alternative solutions to the problem mentioned above.

Of course solution 4 causes more work for implementers, since
type checking must now be done on an entire system rather than a
single class. But that's what computers are for: tedious, exhaustive
work, especially when (as here) the consequences of not doing that
work are potentially rather damaging. In earlier articles I have
described a strategy for doing the work in an incremental fashion
so that its performance overhead is acceptable.  

As an aside (meant to be thought- rather than flame-provoking)
I have come to realize, although not early enough, that
information hiding is a greatly overrated goal. What counts
is abstraction.  Information hiding is often a convenient
syntactical way to help achieve abstraction, but is a means
rather than an end, and is not desirable in 100% of cases.
It must always be tempered with other criteria.

Just because David Parnas and many others after him
(including the undersigned) have emphasized the potential benefits
of information hiding in well-defined cases does not make
incantation a substitute for thinking.

(Another of these incantations is ``implementation inheritance''.
This does not mean anything. In class POLYGON, why would
``add_vertex'' be implementation and ``access_vertex''
specification?)

All too often we forget that design methodology and programming
languages are meant to help people produce good software, not
to restrict their creative freedom.
-- 
-- Bertrand Meyer
bertrand@eiffel.com

rick@tetrauk.UUCP (Rick Jones) (01/31/91)

Given that this thread is starting to look like an Eiffel v C++ debate, there
is a significant aspect of Eiffel which has been overlooked - assertions.

One of the concepts of Eiffel is that the properties of a class may (and
should) be specified in terms of the class invariant and pre- and
post-conditions of routines.  When using inheritance to write a new class, you
can access and/or redefine anything in the parent class, but you cannot
override the assertions.  (In the current version you can override the pre- and
post-conditions, but this is a recognised compiler problem which is going to be
rectified.)  You certainly can't override the invariant, but you can strengthen
it by adding additional invariant properties.

The correct way to use Eiffel is to be rigorous with the use of assertions, and
run all your tests with all assertion checking on.  The assertion checking may
then be turned off for a production version once you are sure that the
assertions never get violated.

This is a sometimes subtle but significantly different approach to the problem
of protecting the semantics of classes, and avoids the need to hide the
implementation details from descendant classes.  You can look at it by saying
that the interface of a class is public, the implementation is protected, and
the assertions are private (it's not an exact analogy, but it gives the right
feel).

Of course you have to get your assertions correct, but I don't believe that is
any harder than getting the public/private/protected bit right.

I have already had several occasions to be glad of Eiffel's assertion system,
since it has been able to nip in the bud a number of errors which would
otherwise have gone unnoticed and become obscure time-bomb bugs.
-- 
Rick Jones
Tetra Ltd.  Maidenhead, 	Was it something important?  Maybe not
Berks, UK			What was it you wanted?  Tell me again I forgot
rick@tetrauk.uucp					-- Bob Dylan

chip@tct.uucp (Chip Salzenberg) (02/01/91)

According to bertrand@eiffel.UUCP (Bertrand Meyer):
>A class POLYGON exists and has a procedure
>``add_vertex'', which it exports. You want
>to add a class RECTANGLE. What do you do?
>
>1. Decide not to use inheritance.
>2. Redo the existing inheritance hierarchy.
>3. Accept that RECTANGLE inherits from POLYGON and does not export
>   ``add_vertex''. ... and disallow polymorphic assignments of the form
>   p := r where p is of type POLYGON and r of type RECTANGLE.
>4. Accept that RECTANGLE inherits from POLYGON and does not export 
>   ``add_vertex''. Permit polymorphic assignments of the above form;
>   but have the type checker reject as invalid any system which would
>   contain both such an assignment and a call p.add_vertex (...), since
>   it may lead to a run-time inconsistency.

In C++, the add_vertex() member function would almost certainly be
virtual; that is, it would act correctly in cases where a |POLYGON*|
actually points to an object of type |RECTANGLE|.  (If it were not
virtual already, it could be made so by the insertion of the word
"virtual" at one place in the code.)

Given this language feature, add_vertex becomes a non-problem.  The
implementor of RECTANGLE simply provides an appropriate definition of
the RECTANGLE::add_vertex(): a do-nothing function body that returns a
failure indication (if appropriate).

Analyzing this use of C++ results in a fifth item for Bertrand's list:

5.  Accept that RECTANGLE inherits from POLYGON and exports add_vertex,
    and require the programmer to provide an implementation of add_vertex
    appropriate for a RECTANGLE.

In the real world, how is this solution deficient?

>As an aside (meant to be thought- rather than flame-provoking)
>I have come to realize, although not early enough, that
>information hiding is a greatly overrated goal.

Which reminds me of a nice phrase from Leo Brodie's _Thinking_Forth_,
that goes something like this:

    Some programmers use information hiding as if programs are
    prone to attack from roving bands of hostile programmers.

Of course, Forth isn't exactly my favorite programming environment;
I prefer driving with seatbelts (but not a padded cell).  :-)
-- 
Chip Salzenberg at Teltronics/TCT     <chip@tct.uucp>, <uunet!pdn!tct!chip>
 "I want to mention that my opinions whether real or not are MY opinions."
             -- the inevitable William "Billy" Steinmetz

barmar@think.com (Barry Margolin) (02/01/91)

In article <488@eiffel.UUCP> bertrand@eiffel.UUCP (Bertrand Meyer) writes:
>4. Accept that RECTANGLE inherits from POLYGON and does not export 
>``add_vertex''. Permit polymorphic assignments of the above form;
>but have the type checker reject as invalid any system which would
>contain both such an assignment and a call p.add_vertex (...), since
>it may lead to a run-time inconsistency. Do not introduce
>any distinction between ``implementation inheritance'' and any other
>kind.
>
>Based on not inconsiderable experience with the design of O-O systems
>and class hierarchies, I believe that solution 4 is the only reasonable
>one. I would be interested to see any technical counter-argument,
>and alternative solutions to the problem mentioned above.

If static type checking is a requirement, I believe you are right.
However, in OO systems with dynamic type checking and method dispatch,
there is another solution:

5. Require that add_vertex (or, generally, all methods) be a virtual
function (in languages that require such a distinction to be made).  Accept
that RECTANGLE inherits from POLYGON.  Override add_vertex in the RECTANGLE
class with a function that reports an error.  Permit polymorphic
assignments of the form p=r, and accept the fact that it is possible for
p.add_vertex(...) to signal an error at runtime.  This is not a realistic
solution in a language without a decent exception handling mechanism, but
that's an excuse for adding exception handling, not for crippling programs.

Furthermore, some languages (e.g. CLOS) even allow the class of an object
to change dynamically, so there's another solution:

6. As in 5, use virtual function inheritance of add_vertex.
RECTANGLE::add_vertex would change the class of self to POLYGON and then
call self.add_vertex (which will invoke the POLYGON::add_method, which
might further change the class to PENTAGON).

In fact, schemes like this may be the only sensible way to implement a
system of this type.  Consider a context in which the above classes would
be used: a graphical drawing program.  The user clicks on an object and
then selects an operation from a menu (or vice versa).  Solution 6 would be
useful if you want to allow users to use the rectangle tool to create an
object, but later permit them to edit it in such a way that it is no longer
a rectangle.  Solution 5 is useful if the UI design specifies that objects
created using the rectangle tool must always stay rectangular; when the
user tries to add a vertex to the object, an error message would be put up
telling them that the operation is not appropriate for that object.  The
logic necessary to put up this warning must exist in the program somewhere,
so why not embed it directly in the methods that need it?  With solution 4,
the code to select an object could not use polymorphic assignment, because
eventually the object will be passed to the add_vertex function; it would
probably have to use a discriminated union, and all future uses of that
union would have to dispatch on the discriminant (isn't this what OO
programming was supposed to relieve us from?).

If the menu system supports "greying out" inappropriate menu items, this
could be done by having a virtual list_allowed_operations or
is_operation_allowed method.  Either way, I think it would be very
difficult to structure the program in such a way that a static type checker
could verify that add_vertex is only called on POLYGONs that aren't
specialized to a particular number of vertices, unless is_operation_allowed
were made a primitive of the language (actually, it's a good idea to have
such a primitive, because it would be easy for user-defined functions like
this to get out of sync with the actual code).

--
Barry Margolin, Thinking Machines Corp.

barmar@think.com
{uunet,harvard}!think!barmar

mario@cs.man.ac.uk (Mario Wolczko) (02/01/91)

In article <488@eiffel.UUCP>, bertrand@eiffel.UUCP (Bertrand Meyer) writes:
> 
> 	Anyone who wants to argue that the Eiffel policy is not the
> appropriate one is going to have to leave pious generalities
> and offer serious technical arguments.
and then offers some design alternatives...

...but the options he then lists are not exhaustive.  Eiffel allows
the programmer to control the visibility of any feature to clients via
the export clause.  The problem is that *all* features are visible to
children.  If, in the implementation of a class (especially a deferred
class), the programmer needs a feature (be it a routine or attribute)
for purely private use, and which can be provided in a number of
different forms, then it should be possible for the programmer to say,
``Don't assume that this will be present in this form in all versions
of this class'', ie that children should not be able to use this
feature.  That's what the protected mode of C++ is for.  If the
feature is protected, then the implementor of the class is free to
change or remove it, provided that the advertised interfaces (to
clients and children) work as advertised!

An inability to do this violates what I call "the Principle of
Decomposition": at any time the programmer should be free to decompose
one thing into more than one thing, without affecting the advertised
interface or behaviour.  If you mention this in isolation, most people
nod in agreement.  But no mainstream OO language satisfies it!  If we
substitute "feature" for "thing", then Eiffel fails because of the
described behaviour: the extra features appear in the subclass
interface.  If we substitute "class" for "thing" (ie decompose one
class into two classes, related by inheritance), then *no* mainstream
OO language can satisfy the principle, because one cannot localise the
interaction between parent and child (self/this/current is always
bound in the class of the receiver, which may be a descendant of the
child).

None of this is new to OOP: it's a problem in any ADT theory/language:
Can you implement any ADT and not have the implementation show
through?  The philosophical point is whether you feel that the
parent/child boundary consititutes an interface at which hiding should
be possible.  I do; I know Dave Ungar doesn't (he told me); I'd be
interested to hear what Bertrand Meyer thinks.

I think the point is well-argued in Snyder's paper [1], and I was
surprised when I came across Eiffel that it did not adopt this policy
(especially in view of its "software engineering" stance).  I feel
sure that Bertrand Meyer was aware of this paper when designing
Eiffel, so perhaps he could explain his decision to do otherwise?

> All too often we forget that design methodology and programming
> languages are meant to help people produce good software, not
> to restrict their creative freedom.
Quite.  I want the freedom to create classes with *exactly* the
interfaces (client & child) I want.

[1] Alan Snyder, Inheritance and the Development of Encapsulated
Software Components, Research Directions in Object-Oriented
Programming, MIT Press, 1987, pp147-64 (an earlier version appears in
OOPSLA 86).

Mario Wolczko

   ______      Dept. of Computer Science   Internet:      mario@cs.man.ac.uk
 /~      ~\    The University              uucp:      mcsun!ukc!man.cs!mario
(    __    )   Manchester M13 9PL          JANET:         mario@uk.ac.man.cs
 `-':  :`-'    U.K.                        Tel: +44-61-275 6146  (FAX: 6280)
____;  ;_____________the mushroom project___________________________________

doug@saturn.ads.com (Doug Morgan) (02/01/91)

In article <488@eiffel.UUCP> bertrand@eiffel.UUCP (Bertrand Meyer) writes:

	   Will anyone take up the simplest example (discussed in my
   posting <485@eiffel.UUCP>): a class POLYGON exists and has
   a procedure ``add_vertex'', which it exports.
   You want to add a class RECTANGLE. What do you do?

   1. ...
   2. ...
   3. ...
   4. Accept that RECTANGLE inherits from POLYGON and does not export 
   ``add_vertex''. Permit polymorphic assignments of the above form;
   but have the type checker reject as invalid any system which would
   contain both such an assignment and a call p.add_vertex (...), since
   it may lead to a run-time inconsistency. Do not introduce
   any distinction between ``implementation inheritance'' and any other
   kind.

   ... I would be interested to see any ... alternative solutions ...
   -- 
   -- Bertrand Meyer
   bertrand@eiffel.com

4. is a bit strict for my taste.  I wouldn't mind it as a option,
however.  I like being able to do this (say, in CLOS):

5. Accept that RECTANGLE inherits from POLYGON and *does* export
``add_vertex''. Permit polymorphic assignments of the form p := r
where p is of type POLYGON and r of type RECTANGLE and have a
subsequent call of p.add_vertex (...)  convert the current RECTANGLE
implementation (and class) of object p to a POLYGON implementation
(and class).

Of course, for this example I changed the "invariants" for the
RECTANGLE class.  They are not unreasonable, just beyond the
compile-time restrictions of Eiffel.

Doug Morgan
doug@ads.com

tynor@hydra.gatech.edu (Steve Tynor) (02/01/91)

>From: barmar@think.com (Barry Margolin)
...
>that RECTANGLE inherits from POLYGON.  Override add_vertex in the RECTANGLE
>class with a function that reports an error.  Permit polymorphic
>assignments of the form p=r, and accept the fact that it is possible for
>p.add_vertex(...) to signal an error at runtime.  This is not a realistic
>solution in a language without a decent exception handling mechanism, but
>that's an excuse for adding exception handling, not for crippling programs.

The problem with this sort of solution is that it defers detection of the error
until run-time. It is very difficult to statically examine your code to
determine whether your program will fail (you have to do a dataflow analysis
which is hard in general). I've run into more than a few latent bugs in large
Flavors and C++ programs because of this sort of behavior. [ Before someone
jumps on my case telling me about C++ pure virtuals, I know about them - in the
case I'm describing, they were not used appropriately by the class designer ].

I'm a big believer in doing as much static code analysis as you (or better yet,
the compiler) can - I realize that it is not always feasible (eg. in dynamic
systems like CLOS and Flavors).

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
"Language is a virus from outer space" - William S. Burroughs
                     
    Steve Tynor
    Georgia Tech Research Institute
    Artificial Intelligence Branch
    tynor@prism.gatech.edu

barmar@think.com (Barry Margolin) (02/01/91)

In article <TYNOR.91Jan31145748@hydra.gatech.edu> tynor@hydra.gatech.edu (Steve Tynor) writes:
>>From: barmar@think.com (Barry Margolin)
>...
>>that RECTANGLE inherits from POLYGON.  Override add_vertex in the RECTANGLE
>>class with a function that reports an error.
>The problem with this sort of solution is that it defers detection of the error
>until run-time. It is very difficult to statically examine your code to

Isn't that what I said?  Here's the first paragraph of the posting you
quoted above:

>>If static type checking is a requirement, I believe you are right.
>>However, in OO systems with dynamic type checking and method dispatch,
>>there is another solution:

Also, do you have any response to my point that the programmer will have to
contort his program so that it is possible to statically determine that it
meets all type constraints?  Maybe it's my Lisp upbringing, but I believe
that programs that make good use of OO are hard to write in such a way that
they can be statically type-checked, unless you implement extreme
restrictions (such as disallowing assigning derived class instances to
ancestor class variables, i.e. the original solution 3).

I didn't say it explicitly in my previous post, but I think solution 4
(allow assigning p=r, but have the compiler try to check that there are no
code paths that would result in RECTANGLE::add_vertex being called)
essentially forces the programmer to code as if solution 3 (disallowing the
assignment) were used in many real-world applications.  The program must
somehow remember that not all POLYGON operations can be performed on p
after such an assignment.  But POLYGON has no idea which of its methods
some derived class might someday disallow, so it must assume any of them
may be disallowed; in this case, why bother assigning to p at all?  I
suppose you could assume that most methods won't be disallowed, and wait
until the compiler warns you of a violation.

Of course, this whole idea of performing a complete flow analysis assumes
that all the information is available to the compiler.  If one is deriving
from a class library for which only the binary and specification are
available, this will be kind of difficult, unless the binary somehow
includes a useful representation of all the flow dependencies.

--
Barry Margolin, Thinking Machines Corp.

barmar@think.com
{uunet,harvard}!think!barmar

tynor@hydra.gatech.edu (Steve Tynor) (02/01/91)

In article <1991Jan31.213146.4306@Think.COM> barmar@think.com (Barry Margolin) writes:
>>>that RECTANGLE inherits from POLYGON.  Override add_vertex in the RECTANGLE
>>>class with a function that reports an error.
>>The problem with this sort of solution is that it defers detection of the error
>>until run-time. It is very difficult to statically examine your code to
>
>Isn't that what I said?  Here's the first paragraph of the posting you
>quoted above:

Quite right, my appologies, etc. My post was a little too knee-jerkish.

>Also, do you have any response to my point that the programmer will have to
>contort his program so that it is possible to statically determine that it

I'd best wait to respond lest sin again... 

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
inherit STD_DISCLAIMER;
                     
    Steve Tynor
    Georgia Tech Research Institute
    Artificial Intelligence Branch
    tynor@prism.gatech.edu

joel@decwrl.dec.com (Joel McCormack) (02/01/91)

>that RECTANGLE inherits from POLYGON.  Override add_vertex in the RECTANGLE
>class with a function that reports an error.  Permit polymorphic
>assignments of the form p=r, and accept the fact that it is possible for
>p.add_vertex(...) to signal an error at runtime.  This is not a realistic
>solution in a language without a decent exception handling mechanism, but
>that's an excuse for adding exception handling, not for crippling programs.

  The problem with this sort of solution is that it defers detection of the
  error until run-time. It is very difficult to statically examine your code to
  determine whether your program will fail (you have to do a dataflow analysis
  which is hard in general).

Note that the current Eiffel solution, which is to detect a type error only when given a specific system of modules, has the same problem if dynamic loading of modules is allowed--the error cannot be detected until runtime.  You can detect the inconsistency at the time you try to load the module, but this may be hours or days into running the program.  The user interface editor is a particularly good example: you'd like to be able to write an extendable editor that can deal with subclassess it has never se






en, merely by naming them rather than linking against them.

Note that dynamic loading creates other similar problems in trying to defer decisions until a ``system'' of modules is known, because the system of modules is open-ended.  You can't do global register allocation, you can't defer decisions about what methods can be inlined, etc.

- Joel McCormack (decwrl!joel, joel@decwrl.dec.com)

joel@decwrl.dec.com (Joel McCormack) (02/01/91)

xrn evidently ate one of my paragraphs, and didn't wrap lines automatically.
Gotta check my .Xdefaults, I guess...

>that RECTANGLE inherits from POLYGON.  Override add_vertex in the RECTANGLE
>class with a function that reports an error.  Permit polymorphic
>assignments of the form p=r, and accept the fact that it is possible for
>p.add_vertex(...) to signal an error at runtime.  This is not a realistic
>solution in a language without a decent exception handling mechanism, but
>that's an excuse for adding exception handling, not for crippling programs.

  The problem with this sort of solution is that it defers detection of the
  error until run-time. It is very difficult to statically examine your code to
  determine whether your program will fail (you have to do a dataflow analysis
  which is hard in general).

Note that the current Eiffel solution, which is to detect a type error only
when given a specific system of modules, has the same problem if dynamic
loading of modules is allowed--the error cannot be detected until runtime. 
You can detect the inconsistency at the time you try to load the module, but
this may be hours or days into running the program.  The user interface editor
is a particularly good example: you'd like to be able to write an extendable
editor that can deal with subclassess it has never seen, merely by naming them
rather than linking against them.

Note that dynamic loading creates other similar problems in trying to defer
decisions until a ``system'' of modules is known, because the system of
modules is open-ended.  You can't do global register allocation, you can't
defer decisions about what methods can be inlined, etc.

-- 
- Joel McCormack (decwrl!joel, joel@decwrl.dec.com)

craig@Neon.Stanford.EDU (Craig D. Chambers) (02/01/91)

In article <488@eiffel.UUCP> bertrand@eiffel.UUCP (Bertrand Meyer) writes:
>
>	Will anyone take up the simplest example (discussed in my
>posting <485@eiffel.UUCP>): a class POLYGON exists and has
>a procedure ``add_vertex'', which it exports.
>You want to add a class RECTANGLE. What do you do?
>
>3. Accept that RECTANGLE inherits from POLYGON and does not export
>``add_vertex''. (This procedure violates the invariant of class
>RECTANGLE anyway, so the consistency rules forbid it to be exported,
>but of course non-exported routines do not have to maintain the
>invariant.) To avoid burdening the compiler writer,
>call this ``implementation inheritance'' and disallow
>polymorphic assignments of the form p := r where p is of type
>POLYGON and r of type RECTANGLE.
>
>4. Accept that RECTANGLE inherits from POLYGON and does not export 
>``add_vertex''. Permit polymorphic assignments of the above form;
>but have the type checker reject as invalid any system which would
>contain both such an assignment and a call p.add_vertex (...), since
>it may lead to a run-time inconsistency. Do not introduce
>any distinction between ``implementation inheritance'' and any other
>kind.
>
>Based on not inconsiderable experience with the design of O-O systems
>and class hierarchies, I believe that solution 4 is the only reasonable
>one. I would be interested to see any technical counter-argument,
>and alternative solutions to the problem mentioned above.
>
>Of course solution 4 causes more work for implementers, since
>type checking must now be done on an entire system rather than a
>single class. But that's what computers are for: tedious, exhaustive
>work, especially when (as here) the consequences of not doing that
>work are potentially rather damaging. In earlier articles I have
>described a strategy for doing the work in an incremental fashion
>so that its performance overhead is acceptable.  

This issue has bothered me for a while, but I've not posted about it
before.  Now with this challenge, I can't resist.

Option 3 seems like the only reasonable solution to me.  And I'm not
arguing from an implementor's point of view; I'm not averse to a
complex implementation if it supports a nice programming model and
environment.  The real problem with option 4 is that a large existing
legal program can be invalidated by a single added line.

Say you have a large application manipulating various shapes,
including rectangles and polygons as above.  Now say someone adds a
single line to the program, say an assignment "p := r".  If in the
rest of the application there are no such assignments, but many calls
on p.add_vertex, then all these calls are suddenly invalidated by the
new assignment.  This seems incredibly wrong.  The legality of earlier
add_vertex calls shouldn't depend on whether some future programmer
makes an assignment "p := r"; instead, the new assignment should be
marked illegal.  This exactly corresponds to option 3: making
rectangle inherit code from polygon, but not making rectangle a
subtype of polygon.

Consider the opposite scenario: In a large application, there are
countless assignments of the form "p := r", but no calls of
p.add_vertex.  The environment could allow this to happen, and make
rectangle a legal subtype of polygon, by pretending that the
add_vertex feature didn't exist (after all, the application didn't
need it).  Then some programmer adds a call to p.add_vertex.  Again,
this last call should be marked in error, since earlier treatment of
rectangles as subtypes of polygons has disallowed any future use of
add_vertex.  This approach is a variant of option 3, with the
extension that unused features can be ignored when forcing one class
to be a supertype of another.  This feature is extra-lingual (part of
the environment), but may be nice to have around to support reuse of
parts of existing code (e.g. the rectangle class is reusing
(inheriting from) part of the polygon class by pretending that certain
unneeded operations like add_vertex aren't there).

So I conclude, on the grounds of practical programming concerns and
usability of the resulting language, that option 3 is far preferable
to option 4.

Actually, option 6 proposed by Barry Margolin (changing the class of a
rectangle to a polygon after an add_vertex) may be a useful
alternative in some situations.  A more general, cleaner facility for
changing the implementation of an object at run-time is called dynamic
inheritance in Self.  Use of this feature would require distinguishing
interface from representation/implementation, with rectangle being a
particular representation of polygon but not a distinct type.  Then
after the add_vertex, the representation of a polygon that was a
rectangle changes, but not its type (it's still a polygon).

None of these options works nicely in all conceivable situations, but
instead imposes certain restrictions on use (limits what the
programmer can write).  I argue that the restrictions imposed by
option 3 (assignments require subtype compatibility) and/or option 6
(some subclasses are not separate types) are more reasonable and
likely to be acceptable in practice than those for option 4 (existing
programs may be invalidated by new code).  However, if the programmer
needs to manipulate both rectangles and polygons and assign between
them, do add_vertex's on polygons, and treat rectangles as a distinct
type (have additional behavior for rectangles), then none of these
static solutions works.  The remaining solution is to perform dynamic
type-checking (or, equivalently, conditional assignment in Eiffel) on
calls of p.add_vertex to verify that p doesn't contain a rectangle, or
to make a version of add_vertex for rectangles that signals a run-time
error.

-- Craig Chambers

rick@tetrauk.UUCP (Rick Jones) (02/01/91)

In article <488@eiffel.UUCP> bertrand@eiffel.UUCP (Bertrand Meyer) writes:
>
>	Will anyone take up the simplest example (discussed in my
>posting <485@eiffel.UUCP>): a class POLYGON exists and has
>a procedure ``add_vertex'', which it exports.
>You want to add a class RECTANGLE. What do you do?

This thread seems to me to be exposing one of the fundamental problems of
inheritance, which is the distinction between mutable and imutable objects.

Given that inheritance implies both specialisation and extension, then if
objects are considered fixed once created there is no problem: a rectangle is
clearly a descendant of polygon, since it is a specialised version.  Everywhere
that a fixed polygon may be used, then a fixed rectangle may be used, so a
rectangle is type conformant with a polygon.

However, when we allow objects to be modified we are seriously challenging the
whole concept of taxonomy.  In fact in the shapes example, the type conformance
appears to be inverted.  In terms of modifying vertices, the polygon is type
conformant with a rectangle.

Both the CLOS solution of dynamically changing the object's class, and Bertrand
Meyer's solution of extended compiler type checking, seem to be ultimately
trying to address this problem.  Both have their advantages and drawbacks.  One
problem I see with allowing the class to change is that you might not want this
to happen inadvertently.  Bertrand's solution covers a more general aspect of
what might be called "partial inheritance", but I can see the sort of errors
the compiler might generate proving very frustrating for programmers, and lead
to all sorts of ugly cross-assignments being used in order to keep the compiler
quiet.  This sort of code could also well appear pointless on first reading,
and confuse novice programmers (this is speculation, since I have not yet had
to live with such a compiler).

Has anyone tried to address this problem by considering that a class
essentially has two interfaces?  One - the "read" interface - provides
information about the object in its current (abstract) state.  The other - the
"write" interface - offers facilities to change the object.  It seems that if
the language supported the ability to define not only the class TYPE of a
variable, but also the class USAGE (read or write), then the restrictions would
be clearer, both to the compiler and to the programmer.  It's a little like the
C++ "const" qualifier, but taking the semantic implications a lot further.

Eiffel would be well suited to handle this notion, since it already
distinguishes between functions/attributes and procedures.  The concept of
declaring an Eiffel reference as "read-only" would simply prevent it being used
to call procedures of the class - only access to attributes and functions would
be allowed (good Eiffel design avoids the use of functions which have side
effects on the the object's abstract state).  This is not a substitute for
Bertrand's more general global type-checking, but would make code clearer in
representing the current use of the object, and catch errors earlier.

I believe it could also simplify the global typing problem, since read-only
variables would already be limited as to the range of features available to
them, but I haven't had time to consider this in any depth.  Have any of the
theorists out there done any work along these lines?

-- 
Rick Jones
Tetra Ltd.  Maidenhead, 	Was it something important?  Maybe not
Berks, UK			What was it you wanted?  Tell me again I forgot
rick@tetrauk.uucp					-- Bob Dylan

sakkinen@tukki.jyu.fi (Markku Sakkinen) (02/01/91)

In article <27A86309.281A@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes:
>According to bertrand@eiffel.UUCP (Bertrand Meyer):
>>A class POLYGON exists and has a procedure
>>``add_vertex'', which it exports. You want
>>to add a class RECTANGLE. What do you do?
>>
>>1. Decide not to use inheritance.
>>2. [...]
>> ...

>In C++, the add_vertex() member function would almost certainly be
>virtual; that is, it would act correctly in cases where a |POLYGON*|
>actually points to an object of type |RECTANGLE|.  (If it were not
>virtual already, it could be made so by the insertion of the word
>"virtual" at one place in the code.)

Sorry, that was not the difficulty.  On the contrary, in Eiffel,
Smalltalk, and many other OOPL's _all_ procedures are virtual
in the Simula/C++ sense.

>Given this language feature, add_vertex becomes a non-problem.  The
>implementor of RECTANGLE simply provides an appropriate definition of
>the RECTANGLE::add_vertex(): a do-nothing function body that returns a
>failure indication (if appropriate).

This is only possible if either:
1. 'add_vertex' was originally declared with a return value or parameter
   that indicates success or failure.  One would not like to add such
   parameters to the majority of procedures, because they tend to
   complicate the code too much.
2. The language has an exception mechanism;  incidentally, Eiffel has
   it but C++ hasn't - well, there is a suggestion in the Ellis &
   Stroustrup book.

>Analyzing this use of C++ results in a fifth item for Bertrand's list:
>
>5.  Accept that RECTANGLE inherits from POLYGON and exports add_vertex,
>    and require the programmer to provide an implementation of add_vertex
>    appropriate for a RECTANGLE.
>
>In the real world, how is this solution deficient?

The defect of this solution is that _all_ inapproriate invocations
of 'add_vertex' are detected only at run time.  Some other solutions
make it possible to classify the invocations into three classes
at compile time:
1. Those that are certainly correct.
2. Those that are certainly wrong and cause an error message.
3. Those that may be right or wrong and need a run-time check.

The bad news is that one must limit flexibility and polymorphism
if one wants to avoid run-time checks completely.
(I'll return to this in another posting.)

Markku Sakkinen
Department of Computer Science and Information Systems
University of Jyvaskyla (a's with umlauts)
PL 35
SF-40351 Jyvaskyla (umlauts again)
Finland
          SAKKINEN@FINJYU.bitnet (alternative network address)

sakkinen@tukki.jyu.fi (Markku Sakkinen) (02/01/91)

In article <TYNOR.91Jan31145748@hydra.gatech.edu> tynor@hydra.gatech.edu (Steve Tynor) writes:
>>From: barmar@think.com (Barry Margolin)
>...
>>that RECTANGLE inherits from POLYGON.  Override add_vertex in the RECTANGLE
>>class with a function that reports an error.  Permit polymorphic
>>assignments of the form p=r, and accept the fact that it is possible for
>>p.add_vertex(...) to signal an error at runtime.  This is not a realistic
>>solution in a language without a decent exception handling mechanism, but
>>that's an excuse for adding exception handling, not for crippling programs.
>
>The problem with this sort of solution is that it defers detection of the error
>until run-time. It is very difficult to statically examine your code to
>determine whether your program will fail (you have to do a dataflow analysis
>which is hard in general).  [...]
> ...

Indeed, not only "very difficult", but in many cases even theoretically
impossible.

>I'm a big believer in doing as much static code analysis as you (or better yet,
>the compiler) can - I realize that it is not always feasible (eg. in dynamic
>systems like CLOS and Flavors).

I sympathise, but even in languages that are essentially statically typed
there are good reasons for some facilities that require run-time checks.
The OOPSLA/ECOOP'90 proceedings contains at least two papers that tackle
these issues: Lehrmann Madsen & Magnusson & Moller-Pedersen,
Palsberg & Schwartzbach.

Someone may note that I am here defending run-time checks, while in the
immediately preceding posting I was rather on the side of static checking.
To adapt a famous Einstein quote:
Things should be checked at compile time as much as possible,
but no more.

Markku Sakkinen
Department of Computer Science and Information Systems
University of Jyvaskyla (a's with umlauts)
PL 35
SF-40351 Jyvaskyla (umlauts again)
Finland
          SAKKINEN@FINJYU.bitnet (alternative network address)

jacob@gore.com (Jacob Gore) (02/02/91)

/ comp.lang.eiffel / bertrand@eiffel.UUCP (Bertrand Meyer) / Jan 30, 1991 /
> 	Will anyone take up the simplest example (discussed in my
> posting <485@eiffel.UUCP>): a class POLYGON exists and has
> a procedure ``add_vertex'', which it exports.
> You want to add a class RECTANGLE. What do you do?

As long as we combine the subtyping (specification) hierarchy with
implementation reuse hierarchy (I'm not saying it's a bad thing, although
I'm beginning to seriously wonder), the option most obvious to me is:

5. RECTANGLE needs to replace the 'add_vertex' it inherits with one that
does nothing.

I don't see why RECTANGLE needs to welch on the contract of its superclass
in the simplest case, and I don't think it should.

However, if POLYGON's 'add_vertex' contains in its contract the
postcondition "num_vertices = num_vertices + 1", there is no way RECTANGLE
can uphold the contract.

So, I see Bertrand's reasoning behind his option 4, but I am wondering if
there is a fundamentally different approach to the whole thing...

I'd also like to refer people on a rather good letter on this subject, one
that demonstrates what its author sees as a fundamental problem (though
without offering a concrete solution):

	Paul W. Abrahams.  "Subject: Objectivism" (in ACM Forum).
	Communications of the ACM, Vol. 34, No. 1, Jan. 1991, pp. 15-16.

Jacob
--
Jacob Gore		Jacob@Gore.Com			boulder

chip@tct.uucp (Chip Salzenberg) (02/02/91)

According to rick@tetrauk.UUCP (Rick Jones):
>Given that this thread is starting to look like an Eiffel v C++ debate, there
>is a significant aspect of Eiffel which has been overlooked - assertions.

Please don't cloud discussions with irrelevant issues.

Inheritance and information hiding issues are orthagonal to assertion
issues.  A given language may support the former, the latter, both, or
neither.  Let's keep the discussions clear.
-- 
Chip Salzenberg at Teltronics/TCT     <chip@tct.uucp>, <uunet!pdn!tct!chip>
 "I want to mention that my opinions whether real or not are MY opinions."
             -- the inevitable William "Billy" Steinmetz

bertrand@eiffel.UUCP (Bertrand Meyer) (02/02/91)

From <27A86309.281A@tct.uucp> by chip@tct.uucp (Chip Salzenberg):
> >A class POLYGON exists and has a procedure
> >``add_vertex'', which it exports. You want
> >to add a class RECTANGLE. What do you do?
> 
> In C++, the add_vertex() member function would almost certainly be
> virtual; that is, it would act correctly in cases where a |POLYGON*|
> actually points to an object of type |RECTANGLE|.  (If it were not
> virtual already, it could be made so by the insertion of the word
> "virtual" at one place in the code.)

        [As an aside to clarify any misunderstanding: in Eiffel
        all routines are by default dynamically bound (``virtual'').
        Applying static binding when appropriate is the compiler's
        job, not the programmer's.]

> Given this language feature, add_vertex becomes a non-problem.  The
> implementor of RECTANGLE simply provides an appropriate definition of
> the RECTANGLE::add_vertex(): a do-nothing function body that returns a
> failure indication (if appropriate).

A non-problem? For a routine which is supposed to add a vertex,
to do nothing at all is a non-problem?

The postcondition for add_vertex says

    ensure
        number_of_vertices = old number_of_vertices + 1

This is the contract that any implementation must satisfy - or else
(more on the ``or else'' below).

Doing nothing is a gross violation of this contract.

Of course Mr. Salzenberg's solution is not just to ``do nothing''
but to return a ``failure indication''. Returning a failure
indication is not that trivial (especially if we are talking
about a function that already has a result), but it can be
done (if only by adding a global variable), so let's accept it.
But then we have changed the contract! The postcondition is now

ensure
    failure_indication or else
        (number_of_vertices = old number_of_vertices + 1)

which means that the specification of the ORIGINAL ``add_vertex''
routine has now changed! The postcondition has become weaker,
which is the reverse of what is required if existing clients
are to be kept correct.

As a result, every piece of code ever written that included
a call to the old ``add_vertex'' must be updated - just because
someone added the notion or rectangle somewhere. Is that
object-oriented design? Is that the best one can do in C++?

To be sure, a more acceptable of Mr. Salzenberg's scheme
is possible. Instead of a ``do-nothing function body that returns a
failure indication'', one may implement ``add_vertex'' in
RECTANGLE so that it raises an exception - if the language supports
exceptions, of course. This can readily be done in Eiffel;
in fact, the theoretical and practical role of an exception
is precisely to signal that a routine is unable to fulfil its
contract.

This could have been solution number 5 in the original list.
With this approach, there is no need to change existing
client software. But the effect is essentially to renounce
static type checking for run-time checks, an unpleasant
perspective.

All this confirms that, as Rick Jones recently commented here,
one cannot pursue this discussion properly without some reference
to the specification of software elements. Assertions serve
that purpose. Without assertions, you lose any perspective
of the reason why a certain piece of code does what it does,
and for problems such as the one discussed here there is the
ever present risk of falling into mere hacking.

-- Bertrand Meyer
bertrand@eiffel.com

brucec@phoebus.labs.tek.com (Bruce Cohen;;50-662;LP=A;) (02/02/91)

In article <1991Jan31.185534.24530@Think.COM> barmar@think.com (Barry Margolin) writes:
> ...
> Furthermore, some languages (e.g. CLOS) even allow the class of an object
> to change dynamically, so there's another solution:
> 
> 6. As in 5, use virtual function inheritance of add_vertex.
> RECTANGLE::add_vertex would change the class of self to POLYGON and then
> call self.add_vertex (which will invoke the POLYGON::add_method, which
> might further change the class to PENTAGON).
> 
> In fact, schemes like this may be the only sensible way to implement a
> system of this type.  Consider a context in which the above classes would
> be used: a graphical drawing program.  The user clicks on an object and
> then selects an operation from a menu (or vice versa).  Solution 6 would be
> useful if you want to allow users to use the rectangle tool to create an
> object, but later permit them to edit it in such a way that it is no longer
> a rectangle.

There was a lengthy and somewhat inconclusive discussion in this newsgroup
a few months ago of just this scheme in relation to using Self for a
graphic system.  I started it by noting that Self allows this technique in
a graphic system (I hadn't had any real experience with CLOS at the time,
especially the MetaObject Protocol).  As mentioned then, there are
additional benefits to changing the inheritance of an object dynamically:

    tailoring the space and/or time complexity of operations on the object
    to match its current needs (e.g., rectangles which are orthogonal to the
    coordinate axis can be drawn faster than other polygons on many
    systems).

    Structure objects (graphic container objects with semantics about how
    they display their contained graphics) can change their semantics
    easily.  This is handy for modifying the rendering of contained objects
    in job lots.

Most of the benefits are a result of the size and dynamic complexity of
graphics, especially 3D photorealistic graphics.  It's not at all uncommon
to have a system containing hundreds of thousands or millions of primitive
graphic objects, arranged in many complex structures.  Space and time
savings at rendering time become very important in these systems; it's
usually a good tradeoff to lose some time in dynamically modifying the
objects in an editing phase.
--
------------------------------------------------------------------------
Speaker-to-managers, aka
Bruce Cohen, Computer Research Lab        email: brucec@tekchips.labs.tek.com
Tektronix Laboratories, Tektronix, Inc.                phone: (503)627-5241
M/S 50-662, P.O. Box 500, Beaverton, OR  97077

bertrand@eiffel.UUCP (Bertrand Meyer) (02/02/91)

From <1991Feb1.015749.10111@Neon.Stanford.EDU>
by craig@Neon.Stanford.EDU (Craig D. Chambers):

> >
> >a class POLYGON exists and has
> >a procedure ``add_vertex'', which it exports.
> >You want to add a class RECTANGLE. What do you do?
> >
> >3. Accept that RECTANGLE inherits from POLYGON and does not export
> >``add_vertex''. [...] To avoid burdening the compiler writer,
> >call this ``implementation inheritance'' and disallow
> >polymorphic assignments of the form p := r where p is of type
> >POLYGON and r of type RECTANGLE.
> >
> >4. Accept that RECTANGLE inherits from POLYGON and does not export 
> >``add_vertex''. Permit polymorphic assignments of the above form;
> >but have the type checker reject as invalid any system which would
> >contain both such an assignment and a call p.add_vertex (...), since
> >it may lead to a run-time inconsistency. Do not introduce
> >any distinction between ``implementation inheritance'' and any other
> >kind.
> 
> Option 3 seems like the only reasonable solution to me.  And I'm not
> ...
> The real problem with option 4 is that a large existing
> legal program can be invalidated by a single added line.
> 
> Say you have a large application manipulating various shapes,
> including rectangles and polygons as above.  Now say someone adds a
> single line to the program, say an assignment "p := r".  If in the
> rest of the application there are no such assignments, but many calls
> on p.add_vertex, then all these calls are suddenly invalidated by the
> new assignment.  This seems incredibly wrong.  The legality of earlier
> add_vertex calls shouldn't depend on whether some future programmer
> makes an assignment "p := r"; instead, the new assignment should be
> marked illegal.

There is a point here. But you may look at it in the following way.
What is illegal is the presence in the same system of both
of the instructions

[A]		p := r
[B]		p.add_vertex

The error may be in one or in the other. In the situation described
by Mr. Chambers, assume we have an incremental compiler,
and a lot of existing code uses [B]. If someone tries
to add [A] to the system, then this will be flagged by the
incremental compiler, which will point at the most obvious
source of the problem: the new occurrence of [A], not the
existing occurrences of [B]. This invalidates the addition,
not the existing code, which is what Mr. Chambers wants.

In this case we get the effect of solution 3,
but with much less burden on the programmer because many
cases which solution 3 would flag as invalid will be accepted
since they don't raise any problem.

My impression is that uniformly rejecting the cases 
which satisfy 4 but not 3 would significantly restrict the
programmer's power of expression, and that they are
important and useful in practice.









-- 
-- Bertrand Meyer
bertrand@eiffel.com

amanda@iesd.auc.dk (Per Abrahamsen) (02/03/91)

Sorry to interrupt, but why does nobody accept Bertrand Meyers first
two solutions?

>>>>> On 1 Feb 91 16:30:48 GMT, jacob@gore.com (Jacob Gore) said:

Jacob> However, if POLYGON's 'add_vertex' contains in its contract the
Jacob> postcondition "num_vertices = num_vertices + 1", there is no
Jacob> way RECTANGLE can uphold the contract.

But in that case a RECTANGLE is not, conceptually, a POLYGON.  There
are then two possibilities (as Bertrand Meyer mentioned in
<488@eiffel.UUCP>: 

1) The postcondition in correct: You can always add a vertex to a
   polygon. 

   In this case, a rectangle is not a polygon, and it is misleading to
   use inheritance. 

2) The postcondition is wrong.  You can only add a vertex to some
   kinds of polygons.

   If the postcondition is wrong, it should be fixed, like any other
   bug! 

In a program which should be read and understood by other people
later, these are the only long term solutions.  However, when you are
creating a throw away prototype, inheritance might be useful
nonetheless.  In this case, it does not matter much whether the
language provides "implementation inheritance (solution 3), "extended
type check" (solution 4), or "run time check" (solution 5), except
that (5) is the most flexible, and (3) the most safe.

craig@Neon.Stanford.EDU (Craig D. Chambers) (02/04/91)

In article <1081@tetrauk.UUCP> rick@tetrauk.UUCP (Rick Jones) writes:
>Has anyone tried to address this problem by considering that a class
>essentially has two interfaces?  One - the "read" interface - provides
>information about the object in its current (abstract) state.  The other - the
>"write" interface - offers facilities to change the object.  It seems that if
>the language supported the ability to define not only the class TYPE of a
>variable, but also the class USAGE (read or write), then the restrictions would
>be clearer, both to the compiler and to the programmer.  It's a little like the
>C++ "const" qualifier, but taking the semantic implications a lot further.

I've noticed the same read-only v. read-write interface problem.  It
frequently arises in parameterized data types like collections, in
which the read-only interfaces to two differently instantiated
collections are subtype-related in the same way that the instantiating
types are related (read-only-collection[S] <= read-only-collection[T]
iff S <= T, where "<=" means "is a subtype of"), the two write-only
interfaces are related in the opposite way, and consequently the two
read-write interfaces are unrelated.  I described this situation in a
posting to comp.object a while back.

I've been working on a new o-o language design and have been thinking
about ways to allow the programmer to specify "sub-interface"
indications for each member, much in the same way that public v.
private subinterfaces are distinguished in many languages.  Then uses
of the type (e.g. type declarations) could also specify a
subinterface, such as the "read-only" subinterface of a collection.
The present polygon/rectangle example could benefit from
subinterfaces, since the add_vertex operation could be qualified with
the "resizable" subinterface.  I'm concerned a bit about achieving the
desired syntactic conciseness; "read-only array" is a lot longer than
"array".  Maybe the compiler could somehow infer the right
subinterface.

Of course, subinterfaces aren't strictly required, since each
subinterface could just be a separate type/class.  However,
subinterfaces would work well with hierarchies of e.g. collections
each with similar subinterfaces, while the manual method would quickly
get complicated and hard to maintain.  So subinterfaces seem like a
qualitative increase in expressive power.  If they could be integrated
with the already-present public vs. private subinterfaces, then no new
language concepts would be needed (just an existing concept generalized).

>Eiffel would be well suited to handle this notion, since it already
>distinguishes between functions/attributes and procedures.  The concept of
>declaring an Eiffel reference as "read-only" would simply prevent it being used
>to call procedures of the class - only access to attributes and functions would
>be allowed (good Eiffel design avoids the use of functions which have side
>effects on the the object's abstract state).

This sounds like you're proposing a built-in read-only subinterface
much like const in C++.  I would like the various subinterfaces to be
completely user-defined, without any notion of a read-only
subinterface embedded in the language.  Also, in some cases a
function-only subinterface as you suggest could still have the same
subtype problems; the question is whether the types of the arguments
to the function are supertypes of the argument types of overridden
function in the superclass (i.e. the contravariance property of
function subtyping).

>This is not a substitute for
>Bertrand's more general global type-checking, but would make code clearer in
>representing the current use of the object, and catch errors earlier.

Bertrand's type-checking rules are *less general* than subinterfaces,
since what his rule really accomplishes is removing features that are
*never* called from the interfaces of classes (and treating
inheritance links as subtype links only if assignments of the subclass
to the superclass are actually made; this half of the rule doesn't
come into play here).  With subinterfaces, however, different parts of
a class can be ignored in some cases and used in others.  For example,
the add_vertex feature of polygons could be in a special subinterface
marked "resizable", and rectangles could be declared as a subtype of
the non-resizable subinterface of polygons.  Then "p1 := r"
assignments and "p2.add_vertex" calls could appear in the same
program, as long as p1 was declared to be the non-resizable
subinterface of polygons, and p2 was declared as the resizable
interface.  This is not possible with Bertrand's type-checking rules.

-- Craig Chambers

paj@mrcu (Paul Johnson) (02/04/91)

In article <1081@tetrauk.UUCP> rick@tetrauk.UUCP (Rick Jones) writes:
>In article <488@eiffel.UUCP> bertrand@eiffel.UUCP (Bertrand Meyer) writes:
>>
>>	Will anyone take up the simplest example (discussed in my
>>posting <485@eiffel.UUCP>): a class POLYGON exists and has
>>a procedure ``add_vertex'', which it exports.
>>You want to add a class RECTANGLE. What do you do?
>
> [... Much good stuff deleted ...]
>
>Has anyone tried to address this problem by considering that a class
>essentially has two interfaces?  One - the "read" interface -
>provides information about the object in its current (abstract)
>state.  The other - the "write" interface - offers facilities to
>change the object.  It seems that if the language supported the
>ability to define not only the class TYPE of a variable, but also the
>class USAGE (read or write), then the restrictions would be clearer,
>both to the compiler and to the programmer.  It's a little like the
>C++ "const" qualifier, but taking the semantic implications a lot
>further.
>
>Eiffel would be well suited to handle this notion, since it already
>distinguishes between functions/attributes and procedures.  The
>concept of declaring an Eiffel reference as "read-only" would simply
>prevent it being used to call procedures of the class - only access
>to attributes and functions would be allowed (good Eiffel design
>avoids the use of functions which have side effects on the the
>object's abstract state).  This is not a substitute for Bertrand's
>more general global type-checking, but would make code clearer in
>representing the current use of the object, and catch errors earlier.
>
>I believe it could also simplify the global typing problem, since
>read-only variables would already be limited as to the range of
>features available to them, but I haven't had time to consider this
>in any depth.  Have any of the theorists out there done any work
>along these lines?


Yes.  I have been thinking along precisely these lines and have come
up with some ideas which I call "Fine Grain Inheritance".  Below is a
short summary.  I have a couple of papers submitted to `Software --
Practice and Experience' and ECOOP '91.  (No offence to the TOOLS '91
people, just a matter of timing).

I believe you should split your class up into lots and lots of little
superclasses, each implementing one elementary concept (such as "first
item" or "read/write current item") and usually having only one or two
attributes.  Then go through your class heirarchy and split all
classes up into "read-only" and "write-only" varients.  Then implement
them.  Don't be frightened of the large amounts of repeated
inheritance you wind up with, its OK.  Most of your classes will be
deferred and you should make all attributes into deferred functions as
well, hence allowing them to be either functions or variables in
descendant classes.

Hence in Dr. Meyer's example, POLYGON will have (amongst others) an
ancestor LIST[ VERTEX ], which will have ancestors LIST_READ[ VERTEX ]
and LIST_WRITE[ VERTEX ].  RECTANGLE will have (amongst others) an
ancestor LIST_READ[ VERTEX_READ ].  Hence not only is it impossible to
add or remove verteces of RECTANGLE, it is also impossible to move a
vertex (short of reverse assignment).  On the other hand a function
which needs to enumerate the verteces of a shape will be able to
handle both POLYGON and RECTANGLE by taking an argument of type
LIST_READ[ VERTEX_READ ].

This scheme becomes completely flexible when a new type rule (which I
call the "Sibling-Supertype" rule) is added which (put briefly) allows
the following.

    If the supertypes of A are a subset of the supertypes of B, then A
    is a supertype of B.

These ideas allow the development of totally flexible class libraries.
The only problem comes when coarse grain libraries must be reused as
well.

Of course, tools like "flat" and "short" become absolutely vital in
fine grain libraries, as does multiple inheritance.

See you all at TOOLS '91.

Paul.
-- 
Paul Johnson                               UUCP: <world>!mcvax!ukc!gec-mrc!paj
--------------------------------!-------------------------|-------------------
GEC-Marconi Research is not 	| Telex: 995016 GECRES G  | Tel: +44 245 73331
responsible for my opinions.	| Inet: paj@uk.co.gec-mrc | Fax: +44 245 75244

chip@tct.uucp (Chip Salzenberg) (02/05/91)

According to sakkinen@jytko.jyu.fi (Markku Sakkinen):
>In article <27A86309.281A@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes:
>>RECTANGLE::add_vertex(): a do-nothing function body that returns a
>>failure indication (if appropriate).
>
>This is only possible if either:
>1. 'add_vertex' was originally declared with a return value or parameter
>   that indicates success or failure.
>2. The language has an exception mechanism ...

I discern that my suggestion was inadequate.  I withdraw it.

I now agree, however, with those who suggest that RECTANGLE is not an
appropriate derivation of POLYGON.

There is an assumption built into a POLYGON interface that includes an
add_vertex() function: that a POLYGON may be modified in its number of
vertices.  The derivation of RECTANGLE from POLYGON violates that
assumption.  Separate inheritance subtrees of POLYGON, such as
FIXED_POLYGON and VARIABLE_POLYGON, now seem to me the best way to go.
-- 
Chip Salzenberg at Teltronics/TCT     <chip@tct.uucp>, <uunet!pdn!tct!chip>
 "Most of my code is written by myself.  That is why so little gets done."
                 -- Herman "HLLs will never fly" Rubin

pfkeb@ebnextk.SLAC.Stanford.EDU (Paul Kunz) (02/05/91)

In article <27ADD06B.46A3@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes:



   I now agree, however, with those who suggest that RECTANGLE is not an
   appropriate derivation of POLYGON.

   There is an assumption built into a POLYGON interface that includes an
   add_vertex() function: that a POLYGON may be modified in its number of
   vertices.  The derivation of RECTANGLE from POLYGON violates that
   assumption.  Separate inheritance subtrees of POLYGON, such as
   FIXED_POLYGON and VARIABLE_POLYGON, now seem to me the best way to go.
   -- 
   Chip Salzenberg at Teltronics/TCT     <chip@tct.uucp>, <uunet!pdn!tct!chip>
    "Most of my code is written by myself.  That is why so little gets done."
		    -- Herman "HLLs will never fly" Rubin


I've been following this thread a few days but have not had time to
respond but have come to the same conclusion.   If POLYGON is going to
be SuperClass of RECTANGLE (and TRIANGLE and SQUARE) then it can not
possibly have a add_vertex() function.   This is the error.   Only
a very specialized class of VARIABLE_POLYGON which inherits from POLYGON
can have such a method.
   In my mind the confusion comes from the fact that a RECTANGLE (or
TRIANGLE or SQUARE) is something real that we can see, feel, and touch.
They all inherit from something generic, not real, not touchable.  On the
other hand, a n-point POLYGON is also real that can be changed to
n+1 points.   It inherits from something generic as well.   The mistake
is to call this generic thingy a POLYGON and put a add_vertex function
in it.
   That's my two cents.
 
   

caseau@maya.bellcore.com (Yves J Caseau) (02/05/91)

Here is my $0.02 contribution to the problem:
=============================================

1. This is a well-known and common problem. In the type theory and object-oriented
   database community, the example is a class point with an instance variable x,
   and the subclass is the set of point for which x is between 0 and 10. So if we
   change the instance variable, the "class" to which the object belongs must
   change, or an error must be detected.

   The question is: given a class A, and a subclass B which has a characteristic
   property, how to represent it into an object-oriented system?

   Obviously it depends if your system supports such characteristic properties.
   If it does (Eiffel with invariants, Semantic networks with specialisation
   predicates (eg CLASSIC), LORE with selection sets, FOL-based object-oriented 
   systems) you have a "nice" solution: inheritance with automatic error detection
   (Eiffel: the invariant is violated) or reclassification.

   If the system does not support such properties, you have to implement them
   yourself, copying one of the two previous solutions. This means that each
   method that returns an object from B must be specialized (if it is inherited)
   so as to check the characteristic property (and decide between error or 
   re-classification, according to the language features (C++ vs. CLOS)

2. My solution to the POLYGON/RECTANGLE problem.

   I tend to be simple minded:
      a. a RECTANGLE is a POLYGON (thefore there must be inheritance)
      b. Adding a vertex to a polygon produces ANOTHER polygon (a different
         mathematical object)
      c. Thus, rectangle inherit the property add_vertex, which produces
         a pentagone from a rectangle

   Don't flame me on this, I know this is cheating ... but it makes a lot of
   sense.

rmartin@clear.com (Bob Martin) (02/05/91)

In article <27A86309.281A@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes:
>According to bertrand@eiffel.UUCP (Bertrand Meyer):
>>A class POLYGON exists and has a procedure
>>``add_vertex'', which it exports. You want
>>to add a class RECTANGLE. What do you do?
>>
>>1. Decide not to use inheritance.
>>2. Redo the existing inheritance hierarchy.
>>3. Accept that RECTANGLE inherits from POLYGON and does not export
>>   ``add_vertex''. ... and disallow polymorphic assignments of the form
>>   p := r where p is of type POLYGON and r of type RECTANGLE.
>>4. Accept that RECTANGLE inherits from POLYGON and does not export 
>>   ``add_vertex''. Permit polymorphic assignments of the above form;
>>   but have the type checker reject as invalid any system which would
>>   contain both such an assignment and a call p.add_vertex (...), since
>>   it may lead to a run-time inconsistency.
>
>In C++, the add_vertex() member function would almost certainly be
>virtual; [...] add_vertex becomes a non-problem.  The
>implementor of RECTANGLE simply provides an appropriate definition of
>the RECTANGLE::add_vertex(): a do-nothing function body that returns a
>failure indication (if appropriate).
>
>In the real world, how is this solution deficient?

Assume that this example were part of a very large system which
added points to POLYGONs all over the place.  Assume also that
there had never before been a reason for add_point to fail.  Your
proposed solution _changes_ the interface to class POLYGON since
now some POLYGONs (or their derivatives i.e. RECTANGLEs) will 
issue failure for add_point.  Thus it breaks old working code.
>
>>As an aside (meant to be thought- rather than flame-provoking)
>>I have come to realize, although not early enough, that
>>information hiding is a greatly overrated goal.

This is not a flame, it is a provoked thought.  Some people may
overrate a goal.  That does not devalue the goal.  
>

-- 
+-Robert C. Martin-----+:RRR:::CCC:M:::::M:| Nobody is responsible for |
| rmartin@clear.com    |:R::R:C::::M:M:M:M:| my words but me.  I want  |
| uunet!clrcom!rmartin |:RRR::C::::M::M::M:| all the credit, and all   |
+----------------------+:R::R::CCC:M:::::M:| the blame.  So there.     |

adam@visix.com (Adam Kao) (02/06/91)

I have some random thoughts on this topic; Yves J Caseau's posting
brought my ideas together, to the point where I would like to pursue
them further in public.

I have always felt uncomfortable with the emphasis on "Class" in
languages like Smalltalk, C++ etc.  To me, the notion of an Object
seems much more basic and fundamental than a Class.  Everybody knows
that real-world classes are not well-behaved; they are not disjoint,
they are not proper subsets, class membership changes over time.

I see object oriented techniques such as tree heirarchies, multiple
inheritance, etc., as attempts to make Classes well-behaved.  But in
doing so, I feel that we increasingly distance our notion of Class
from the common-sense notion of class, which necessarily makes
object-oriented programming harder to understand.

In the real world, objects do not, strictly speaking, belong to a
class.  A classification system is imposed on real objects by our
world view; class membership is an answer to a question we phrase.
Different question, different answer.

To my mind, classes can be viewed as schema that define invariants
among their members.  Every object is then potentially a member of
as many classes as we care to define.  

Since any trait of an object can be considered a defining invariant
for some class, it then becomes inevitable that any trait change will
remove the object from some classes and add it to others.

An abstract RECTANGLE satisfies all the invariants of a POLYGON, so it
is natural to have RECTANGLE inherit from POLYGON.  But a RECTANGLE
has additional invariants, so operations that are closed over POLYGONs
are not closed over RECTANGLEs.

We know how to add vertices to POLYGONs.  I think it ridiculous to say
we do not know how to add vertices to RECTANGLEs.  The problem is,
adding a vertex to a RECTANGLE gives us something that is not a
RECTANGLE.  

I do not see this as a problem.  Why do we expect that all operations
on an Object must be closed over its Class?

Most languages have type-casting and type-promotion.  In C, we can add
a double to an int, and the result will be a double.  Is this kind of
arithmetic forbidden in object-oriented programming?

To me, the statements "p := r" and "p.add_vertex(...)" are perfectly
compatible.  The "p := r" should imply a cast; if we want to make the
assignment, it means we want to treat rectangles as polygons.  Surely
the explicit conversion "p := r.make_polygon()" followed by
"p.add_vertex(...)" is acceptable; why not accept even
"r.add_vertex(...)" as a shorthand?

I believe that this discussion is evidence that our notion of a Class
is too weak to support all the uses we expect.  Of course, I was
predisposed to this conclusion.  But, I most certainly hope to hear
more discussion on any side of this issue.

Adam

blerner@empire.cs.umass.edu (Barbara Lerner) (02/06/91)

> From: adam@visix.com (Adam Kao)
> 
[..]
> An abstract RECTANGLE satisfies all the invariants of a POLYGON, so it
> is natural to have RECTANGLE inherit from POLYGON.  But a RECTANGLE
> has additional invariants, so operations that are closed over POLYGONs
> are not closed over RECTANGLEs.
> 
> We know how to add vertices to POLYGONs.  I think it ridiculous to say
> we do not know how to add vertices to RECTANGLEs.  The problem is,
> adding a vertex to a RECTANGLE gives us something that is not a
> RECTANGLE.  
> 
> I do not see this as a problem.  Why do we expect that all operations
> on an Object must be closed over its Class?
> 
> Most languages have type-casting and type-promotion.  In C, we can add
> a double to an int, and the result will be a double.  Is this kind of
> arithmetic forbidden in object-oriented programming?
> 
> To me, the statements "p := r" and "p.add_vertex(...)" are perfectly
> compatible.  The "p := r" should imply a cast; if we want to make the
> assignment, it means we want to treat rectangles as polygons.  Surely
> the explicit conversion "p := r.make_polygon()" followed by
> "p.add_vertex(...)" is acceptable; why not accept even
> "r.add_vertex(...)" as a shorthand?
[..]


The distinction between adding a vertex to a polygon and adding a
vertex to a rectangle is the following.  When a vertex is added to a
polygon, no new object is created.  The same polygon object exists but
has one more vertex.

If we take the point of view that we can add a vertex to a rectangle
with the result begin a pentagon, we now have two choices:

    1.  Create a new Pentagon object that is a "copy" of the rectangle
        with the added vertex.
    2.  Add the vertex to the rectangle object, and change its class
        from rectangle to pentagon.

The first solution is fundamentally different from adding a vertex to
the object.  For example, if we have an object O with a field p of
type polygon.

    O.p := make_polygon(..)
    O.p.add_vertex(..);

In this case, O.p will be a polygon with one more vertex than it was
originally created with.

Suppose instead, we do:

    O.p := make_rectangle(..)
    O.p.add_vertex(..);

Now, O.p is still a rectangle since we haven't changed O.p, but
created a new object instead.  Of course, we could say:

    O.p := O.p.add_vertex(..);

This places the burden on the user of add_vertex to make sure that all
references to the rectangle are appropriately updated.  Keep in mind
that there may be more than one object referenceing the rectangle.


The second solution is not type safe in the following situation.
Suppose we have an object O with two fields:  p of type polygon, and r
of type rectangle.  Now suppose we execute the following code with
add_vertex changing the class of the rectangle to pentagon.

    O.r := make_rectangle(..);
    O.p := O.r;
    O.p.add_vertex(..);

If we change the class of the rectangle to pentagon, then O.r now
refers to a pentagon while the class of O.r is declared to be
rectangle.  *Accurately* detecting this situation at compile-time is
probably impossible.  Detecting it at runtime would require every
object to maintain backpointers to all objects that reference it, so
that all class changes could be type-checked.


Barbara Lerner
--

Barbara Staudt Lerner
Deparment of Computer and Information Science
Lederle Graduate Research Center
University of Massachusetts
Amherst, MA  01003

blerner@cs.umass.edu

bertrand@eiffel.UUCP (Bertrand Meyer) (02/07/91)

From <1991Feb5.130359.9735@bellcore.bellcore.com>
by caseau@maya.bellcore.com:

> 1. This is a well-known and common problem. In the type theory and object-oriented
>    database community, the example is a class point with an instance variable x,
>    and the subclass is the set of point for which x is between 0 and 10. So if we
>    change the instance variable, the "class" to which the object belongs must
>    change, or an error must be detected.
> 

It may be a well-known and common problem but I don't remember
reading anything really enlightening in the literature about
the fields mentioned by Yves Caseau.

Here is a way to model Mr. Caseau's example. There is a class A

	class A export
		x, set_x, ...
	feature
		x: INTEGER;

		set_x (new_value: INTEGER) is do x := new_value end
	invariant
		-- Nothing relevant
	end

and an heir B:

	class B export
		x, ...
	feature
		...
	invariant
		0 <= x; x <= 10
	end

	B is fine since its invariant is stronger than the invariant of
the parent class A.

There is a a problem with procedure set_x, however:
the basic rule of class correctness states (in particular) that
every exported routine should preserve the invariant.
(See chapter 7 of OOSC and the article ``Programming as
Contracting''.) Since set_x does not necessarily preserve
the invariant, ergo - it cannot be exported in B.
So we are back to the situation discussed in previous postings,
and the need to allow a class to hide features exported by its
parents.

It is not possible to redefine set_x in B to be more demanding, as in


	class B export
		x, ...
	feature
		...
		set_x (new_value: INTEGER) is
			require
				0 <= new_value; new_value <= 10
			do
				x := new_value
			end
	invariant
		0 <= x; x <= 10
	end


This does not work, since the new precondition would be stronger (again,
see above references). It would make it impossible for a client
to guarantee that a call will be correct through a scheme of the form

[X]
	b1: B;

	...

	if some_consistency_check (my_value) then
		b1.set_x (my_value)
	else
		... Other action ...
	end

There is a conceptual trick, however, that often works. Make
the precondition abstract by introducing an exported function
acceptable_value in A:

	acceptable_value (n: INTEGER): BOOLEAN is
		do Result := true end

and redefine it in B (keeping it exported):

	acceptable_value (n: INTEGER): BOOLEAN is
		do
			Results := (0 <= x) and (x <= 10)
		end

Then you can indeed export set_x in B because clients can use
scheme [X] safely under the following variant:

[Y]
	if acceptable_value (my_value) then
		b1.set_x (my_value)
	else
		... Other action ...
	end

The precondition of set_x does not change in B.

(Of course the concrete precondition
changes since B redefines function acceptable_value;
and it may even appear to have been strengthened,
violating the contracting rules as before. But this is
in fact not true. What counts is the abstract
precondition, as seen from the oustside, by clients; and that
one has remained the same, as expressed by the property
lambda n . acceptable_value (n).)

This technique is used quite frequently in the Basic Eiffel Library.

-- Bertrand Meyer
bertrand@eiffel.com

bertrand@eiffel.UUCP (Bertrand Meyer) (02/07/91)

From <1991Feb5.130359.9735@bellcore.bellcore.com>
by caseau@maya.bellcore.com (Yves J Caseau):

> 2. My solution to the POLYGON/RECTANGLE problem.
> 
>    I tend to be simple minded:
>       a. a RECTANGLE is a POLYGON (thefore there must be inheritance)
>       b. Adding a vertex to a polygon produces ANOTHER polygon (a different
>          mathematical object)
>       c. Thus, rectangle inherit the property add_vertex, which produces
>          a pentagone from a rectangle
> 
>    Don't flame me on this, I know this is cheating ... but it makes a lot of
>    sense.


If one transposes the problem to applicative (rather than imperative)
programming, where there are no procedures but only functions
which do not produce side-effects, and also removes reference
assignment (which introduces aliasing), this solution indeed
works. Mirandeiffel?




-- 
-- Bertrand Meyer
bertrand@eiffel.com

rick@tetrauk.UUCP (Rick Jones) (02/07/91)

In article <27A9AB0C.4794@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes:
>According to rick@tetrauk.UUCP (Rick Jones):
>>Given that this thread is starting to look like an Eiffel v C++ debate, there
>>is a significant aspect of Eiffel which has been overlooked - assertions.
>
>Please don't cloud discussions with irrelevant issues.
>
>Inheritance and information hiding issues are orthagonal to assertion
>issues.  A given language may support the former, the latter, both, or
>neither.  Let's keep the discussions clear.

A significant theme of this thread was the postulation by some that the notion
of "private" was essential in order to prevent subclasses from breaking their
parent's invariants.  The connection between assertion and information hiding
had already been made.

My point is that, as far as maintaining invariants is concerned, there is more
than one way to skin a cat.  In fact my view is that assertions are a superior
method.  If "private" is used to achieve this, then information hiding and
assertions are not being kept separate.  If assertions are used the two issues
are separated.

In summary, I agree with Chip on the orthogonality, but the issue is neither
irrelevant nor, it seems, entirely free of clouds.  A little discussion might
let in some light.
-- 
Rick Jones
Tetra Ltd.  Maidenhead, 	Was it something important?  Maybe not
Berks, UK			What was it you wanted?  Tell me again I forgot
rick@tetrauk.uucp					-- Bob Dylan

craig@Neon.Stanford.EDU (Craig D. Chambers) (02/07/91)

In article <BLERNER.91Feb6100509@empire.cs.umass.edu> blerner@empire.cs.umass.edu (Barbara Lerner) writes:
>
> [stuff deleted]
>
>If we take the point of view that we can add a vertex to a rectangle
>with the result begin a pentagon, we now have two choices:
>
> [stuff deleted]
>
>    2.  Add the vertex to the rectangle object, and change its class
>        from rectangle to pentagon.
>
> [stuff deleted]
>
>The second solution is not type safe in the following situation.
>Suppose we have an object O with two fields:  p of type polygon, and r
>of type rectangle.  Now suppose we execute the following code with
>add_vertex changing the class of the rectangle to pentagon.
>
>    O.r := make_rectangle(..);
>    O.p := O.r;
>    O.p.add_vertex(..);
>
>If we change the class of the rectangle to pentagon, then O.r now
>refers to a pentagon while the class of O.r is declared to be
>rectangle.

If you take the view that rectangle is a specialized *implementation*
of polygons, but the *type* of rectangle is still polygon, then this
class-changing operation is perfectly type-safe (as I mentioned in an
earlier posting).  The add_vertex operation on rectangles modifies the
rectangle *in place* to be a pentagon-represented polygon; the
interface to the rectangle remains unchanged.  This facility requires
that some classes be marked as not defining new types but instead new
implementations of existing types.

I think that enabling the programmer to change the implementation of
objects at run-time is a very powerful feature, and programmers will
find new and useful ways of applying this power as they become used to
thinking about an object's representation and implementation as
mutable.  This operation is rather complicated to implement
efficiently and describe coherently in a class-based language, but is
quite straightforward in a prototype-based language supporting dynamic
object inheritance (delegation) instead of static class inheritance.

-- Craig Chambers

schwartz@karl.cs.psu.edu (Scott Schwartz) (02/07/91)

Bertrand Meyer writes:
  Mirandeiffel?

Hmmm.  Maybe ML can do this, using functors.  Any ML wizards reading this?

--
Scott Schwartz		schwartz@cs.psu.edu

adam@visix.com (Adam Kao) (02/07/91)

Barbara Lerner writes:

> The distinction between adding a vertex to a polygon and adding a
> vertex to a rectangle is the following.  When a vertex is added to a
> polygon, no new object is created.  The same polygon object exists but
> has one more vertex.

> If we take the point of view that we can add a vertex to a rectangle
> with the result begin a pentagon, we now have two choices:

>     1.  Create a new Pentagon object that is a "copy" of the rectangle
>         with the added vertex.
>     2.  Add the vertex to the rectangle object, and change its class
>         from rectangle to pentagon.

Hmm, I guess I did gloss over this distinction.  I do understand the
uses and problems of sharing mutable objects.  Choice (1) defines
nonshared or immutable use, choice (2) defines mutable use.  I see
these choices as complementary.

Our problem with (2) is that someone else can modify the shared
object in ways which I consider illegal.  All users of the object
should agree on the set of allowed changes.

A.  A Class must support all operations of its Parent Class.
B.  A Class must enforce all invariants that users wish to
    preserve on member Objects.

(Allowing (2) makes Class RECTANGLE differ from our specific abstract
rectangle class in the sense defined by (B).  Class membership in the
programming sense is actually only one of many possible invariants
that we might wish to maintain to define an abstract class.)

These goals conflict, which leads me to say that our notion of Class
cannot support all the uses we expect.  (A) and (B) are different
questions; they lead to different classification schemes.

If we choose to preserve (A), then Siblings will be able to change
into each other.  If we choose to preserve (B), then Classes
specialize their Parents by reducing the allowed transformations.

Any solution that tries to define a singular Class concept satisfying
both (A) and (B) will be non-intuitive, at best.

Adam

> Barbara Staudt Lerner
> Deparment of Computer and Information Science
> Lederle Graduate Research Center
> University of Massachusetts
> Amherst, MA  01003

> blerner@cs.umass.edu

PS I notice you edited the followup line to comp.lang.eiffel.
Unfortunately, I know almost nothing about eiffel.  I would rather
continue this discussion in comp.object, or, if you feel it is more
appropriate, via email.

sakkinen@tukki.jyu.fi (Markku Sakkinen) (02/07/91)

In article <BLERNER.91Feb6100509@empire.cs.umass.edu> blerner@empire.cs.umass.edu (Barbara Lerner) writes:
>
>> From: adam@visix.com (Adam Kao)
>> 
>[..]
>> Most languages have type-casting and type-promotion.  In C, we can add
>> a double to an int, and the result will be a double.  Is this kind of
>> arithmetic forbidden in object-oriented programming?
>> 
>> To me, the statements "p := r" and "p.add_vertex(...)" are perfectly
>> compatible.  The "p := r" should imply a cast; if we want to make the
>> assignment, it means we want to treat rectangles as polygons.  Surely
>> the explicit conversion "p := r.make_polygon()" followed by
>> "p.add_vertex(...)" is acceptable; why not accept even
>> "r.add_vertex(...)" as a shorthand?
>[..]

> ...
 [long discussion deleted]
>The second solution is not type safe in the following situation.
>Suppose we have an object O with two fields:  p of type polygon, and r
>of type rectangle.  Now suppose we execute the following code with
>add_vertex changing the class of the rectangle to pentagon.
>
>    O.r := make_rectangle(..);
>    O.p := O.r;
>    O.p.add_vertex(..);
>
>If we change the class of the rectangle to pentagon, then O.r now
>refers to a pentagon while the class of O.r is declared to be
>rectangle.  *Accurately* detecting this situation at compile-time is
>probably impossible.  Detecting it at runtime would require every
>object to maintain backpointers to all objects that reference it, so
>that all class changes could be type-checked.

The analysis was thorough and correct, but it assumed that variables
are always references.  That is indeed the case in almost all well-known
OOPL's, but probably the previous poster meant _value_ and not
reference assignment, as in typical non-OO procedural languages
(Pascal, Ada, C, ...).  Then the sharing problem inherent in reference
semantics simply does not arise.  Many readers of this newsgroup know
that I am certainly not a C++ enthusiast, but one advantage of C++ is
that it allows the programmer to declare both object-valued and
reference-valued variables, depending on which is more suitable
(e.g. semantically) for each situation.

One drawback of C++ is that the dynamic class of an object-valued
variable is always the same as its static class.  Therefore,
assigning (copying, converting) a subclass object to a superclass
variable entails irrecoverable "type loss" (and usually other
information loss too).  Douglas Lea has suggested enhancements
to C++ that would solve this problem to some extent;
these suggestions could be implemented at least by what I would call
"value semantics with reference pragmatics".

In any case, in typical OOPL's (and this goes as well for C++ as for
Smalltalk and Eiffel) a Rectangle class should not inherit any kind
of _concrete_ Polygon class:  the baggage would include not only
some operations that are hardly appropriate for rectangles, but also
unnecessary instance variables.  One could in fact build workable
software easier by making Polygon inherit Rectangle - conflicts between
interface inheritance and implementation inheritance have often been
described in the literature.  The clean way would be to inherit from
a suitably general abstract class.

Markku Sakkinen
Department of Computer Science and Information Systems
University of Jyvaskyla (a's with umlauts)
PL 35
SF-40351 Jyvaskyla (umlauts again)
Finland
          SAKKINEN@FINJYU.bitnet (alternative network address)

sakkinen@tukki.jyu.fi (Markku Sakkinen) (02/07/91)

In article <1991Feb4.223823.3198@runx.oz.au> chrisv@runx.oz.au (Chris Velevitch) writes:
> ...
>Yes I agree that abstraction is the real goal of OOD. I get the feeling
>exporting features is a smoke screen. Would it not be better to simply make
>all features of a class implicitly public (as in Smalltalk)? This would free
>the designer from having to think about what features would be useful to
>clients and help concentrate on what the interface should be in the context
>of the problem.

I agree with the designers of the majority of newer OOPL's that the
Smalltalk way is worse.  It's exactly the question about the interface.
Following Ole-Johan Dahl, the grand old man of OOP, think of objects
or classes offering an interface of procedures (methods) to potential clients,
as an analogy of procedures (in a subroutine library, say) offering
an interface of formal parameters to potential invokers.

The definitions of formal parameters are important to the invokers
of a procedure, but most procedures also contain local variables
that are none of the invokers' business.  Likewise, the definitions
of public operations are important to the clients of a class,
but some private operations may well be useful to implement the
public ones.  In most cases they could be eliminated by code duplication
(contrary to the code reuse goal of OOP),  but that is not possible
if a private procedure is recursive.

If the class designer later notes that some originally private feature
should be offered to clients, he can simply change it from private to public.
From the clients' viewpoint it is exactly equivalent with adding
a brand-new feature to the class.  Our procedure analogy does not work
very well here any longer, but in some languages the formal parameter list
of a procedure can be extended with optional parameters without disrupting
existing code.

I support the "orthogonal" view to visibility that is taken e.g. by
current Simula and C++, that even instance variables can be declared
public or private (or "protected").  Of course, declaring a public instance
variable in a class is a strong commitment that restricts the freedom
in the implementation and evolution of the class, but a class designer
should have the possibility to such a commitment.  _Any_ public feature
constitutes a commitment and restriction, especially if its declaration
promises something about its semantics (like the assertions of Eiffel)
instead of containing only the signature.

Markku Sakkinen
Department of Computer Science and Information Systems
University of Jyvaskyla (a's with umlauts)
PL 35
SF-40351 Jyvaskyla (umlauts again)
Finland
          SAKKINEN@FINJYU.bitnet (alternative network address)

richieb@bony1.bony.com (Richard Bielak) (02/07/91)

In article <1991Feb6.045542.791@visix.com> adam@visix.com (Adam Kao) writes:
>
[...lots of stuff deleted...]

>We know how to add vertices to POLYGONs.  I think it ridiculous to say
>we do not know how to add vertices to RECTANGLEs.  The problem is,
>adding a vertex to a RECTANGLE gives us something that is not a
>RECTANGLE.  
>
>I do not see this as a problem.  Why do we expect that all operations
>on an Object must be closed over its Class?
>

Perhaps the "add_vertex" routine should be a function? How about
this:

       add_vertex : POLYGON is
                do
                        -- whatever
                end;


Then if "r" is a RECTANGLE, we can call "r.add_vertex" and get back
an object that is not a RECTANGLE.



...richie
-- 
+----------------------------------------------------------------------------+
| Richie Bielak  (212)-815-3072    | "The sights one sees at times makes one |
| Internet:      richieb@bony.com  |  wonder if God ever really meant for    |
| Bang:       uunet!bony1!richieb  |  man to fly."  -- Charles Lindbergh     |

om@icsib29 (Stephen M. Omohundro) (02/08/91)

Just a quick note on the Sather solution to the POLYGON vs. RECTANGLE problem.
In Sather the distinction is made between a variable declared as "pa:POLYGON"
which is guaranteed to hold an actual polygon object and "pd:$POLYGON" which can
hold a polygon or any of its descendents. In Sather there is no neccessity for
descendent classes to define all the features of its ancestors (and there is an
"UNDEFINE" mechanism to eliminate features). If the call "pd.add_vertex"
appears, then the compiler checks all descendents of POLYGON and complains if
"add_vertex" is missing from any of them or has an incompatible interface. On
the other hand, the call "pa.add_vertex" is perfectly legal regardless of the
descendent's class definitions. If "add_vertex" is only meant to apply to actual
POLYGON objects, then such declarations guarantee that they will not be applied
to objects of descendent classes. In this way the use of inheritance to reuse
features of a parent class in a child class is somewhat separated from its use
in constraining dynamic dispatch.
-- 

jgk@osc.COM (Joe Keane) (02/08/91)

One possibility is that RECTANGLE and POLYGON are separate concepts which have
nothing to do with each other.  Then there's not much to talk about.

Let's assume that the RECTANGLE class is entirely an optimization.  It's
simply a way of doing POLYGON faster in those cases where you can.  This is
quite likely in a graphics application.  You can do a lot of things more
efficiently if you know something is a rectangle rather than a general
polygon.  For example, the X protocol has special cases for rectangles.

Some routines will know about RECTANGLE and deal with its instances
differently.  Those routines which act on polygons would test if the polygon
is actually a RECTANGLE instance.  There may be some interface which applies
only to RECTANGLE instances.  For example, we may have methods which return
the corners of the rectangle, or the attributes may be directly accesible.

However, most methods do not know about RECTANGLE, only POLYGON.  Given this,
we should insist that the RECTANGLE optimization be transparent.  We should be
able to add a vertex to any polygon, regardless of its implementation class.
That's what polymorphism is all about.  Saying that it works for accessors but
not for modifiers is going only half way.

If we add a vertex to a RECTANGLE instance, then it will have to change its
implementation class to something else.  Perhaps we have a PENTAGON class or
maybe it's just converted back to a generic POLYGON.  Many OO languages don't
deal well with this, although CLOS does.

If a variable is declared as POLYGON, we can add a vertex to it.  It should
have no idea that the implementation class is being shifted from RECTANGLE to
PENTAGON under it.  On the other hand, if a variable is declared as RECTANGLE,
we can't add a vertex.  This is a type error and hopefully will be caught at
compile time.

Global type checking doesn't make sense.  Either an operation is correct or it
isn't.  How can the definition of a class depend on how you use it?  I won't
even talk about the practical problem of having your type checker hunt down
everyone who uses a given class.

sakkinen@tukki.jyu.fi (Markku Sakkinen) (02/08/91)

In article <1991Feb7.151212.25200@bony1.bony.com> richieb@bony1.UUCP (Richard Bielak) writes:
>In article <1991Feb6.045542.791@visix.com> adam@visix.com (Adam Kao) writes:
>>
>[...lots of stuff deleted...]
>
>>We know how to add vertices to POLYGONs.  I think it ridiculous to say
>>we do not know how to add vertices to RECTANGLEs.  The problem is,
>>adding a vertex to a RECTANGLE gives us something that is not a
>>RECTANGLE.  
>>
>>I do not see this as a problem.  Why do we expect that all operations
>>on an Object must be closed over its Class?
>>
>
>Perhaps the "add_vertex" routine should be a function? How about
>this:
>
>       add_vertex : POLYGON is
>                do
>                        -- whatever
>                end;
>
>
>Then if "r" is a RECTANGLE, we can call "r.add_vertex" and get back
>an object that is not a RECTANGLE.

This looked at first like a very good solution to me.
However, it fits best if the interfaces of Polygon and other related
classes are even in general defined in a "functional" fashion:
i.e. modifying operations do not modify the "self" object
but return a new object.  If some operations modify the object itself
and others return a new object instead, the interface is very inconsistent.

The functional approach has its severe drawbacks, too.
Suppose that there are several references from other objects
to this geometric object (initially a rectangle).
If the intended semantics is that the sharing relationship
between these references is maintained (which would be the typical case),
one would need to have back pointers to all those other objects
in order to make them refer to the modified polygon.

Markku Sakkinen
Department of Computer Science and Information Systems
University of Jyvaskyla (a's with umlauts)
PL 35
SF-40351 Jyvaskyla (umlauts again)
Finland
          SAKKINEN@FINJYU.bitnet (alternative network address)

chip@tct.uucp (Chip Salzenberg) (02/08/91)

According to rick@tetrauk.UUCP (Rick Jones):
>In article <27A9AB0C.4794@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes:
>>Inheritance and information hiding issues are orthagonal to assertion
>>issues.  A given language may support the former, the latter, both, or
>>neither.  Let's keep the discussions clear.
>
>The connection between assertion and information hiding
>had already been made.

Okay, I'll grant that point.

My reaction was perhaps stronger than necessary because I don't want
this discussion to degenerate into "my language can beat up your
language."  I'm sorry if I overdid it.

>My point is that, as far as maintaining invariants is concerned, there is more
>than one way to skin a cat.  In fact my view is that assertions are a superior
>method.

I can see a place for both.  There are times when strict invariant
checking would have caught problems in my code.

The GNU C++ compiler includes a "wrapper" mechanism, which replaces
calls to functions with specific return and parameter types with calls
to a wrapper function, one parameter of which is the address of the
"real" function.  This mechanism is too limited for general assertion
checking, however, because of the requirement that each set of return
and parameter types have its own wrapper.

I read about a proposed extension to C++ called A++, or Annotated C++,
by Marshall Cline of Clarkson University and Doug Lea of SUNY.  It
defines assertions for the class ("legal:") and for member functions
("axioms:" with "require" and "promise" assertions).

The resemblance of A++'s assertions to those of Eiffel is acknowledged
in the introduction to the paper, ``Using Annotated C++.''  (Similar
problems lead to similar solutions.)  They hoped to avoid redundant
checks when member functions call each other, and described obvious
code generation tricks to that end.

I wonder if they ever got their proof-of-concept compiler working...

sakkinen@tukki.jyu.fi (Markku Sakkinen) (02/08/91)

In article <10900@pasteur.Berkeley.EDU> om@icsib29 (Stephen M. Omohundro) writes:
>Just a quick note on the Sather solution to the POLYGON vs. RECTANGLE problem.
>In Sather the distinction is made between a variable declared as "pa:POLYGON"
>which is guaranteed to hold an actual polygon object and "pd:$POLYGON" which can
>hold a polygon or any of its descendents. In Sather there is no neccessity for
>descendent classes to define all the features of its ancestors (and there is an
>"UNDEFINE" mechanism to eliminate features). If the call "pd.add_vertex"
>appears, then the compiler checks all descendents of POLYGON and complains if
>"add_vertex" is missing from any of them or has an incompatible interface.
> ...

An obvious implication of the above principles would be that
modifications of classes and methods in a Sather environment
will often cause _massive_ recompilations, wouldn't it?
In particular, if some new class leaves any inherited feature F undefined,
then all clients of all appropriate superclasses must be rechecked.
(If the programming environment registers client relationships to
the utmost detail, those client classes that invoke F through
a "heterogeneous" variable - such as 'pd' above - can be pinpointed
directly.)

Markku Sakkinen
Department of Computer Science and Information Systems
University of Jyvaskyla (a's with umlauts)
PL 35
SF-40351 Jyvaskyla (umlauts again)
Finland
          SAKKINEN@FINJYU.bitnet (alternative network address)

blerner@empire.cs.umass.edu (Barbara Lerner) (02/08/91)

In article <1991Feb7.205232.23548@Neon.Stanford.EDU> craig@Neon.Stanford.EDU (Craig D. Chambers) writes:

>  If you take the view that rectangle is a specialized *implementation*
>  of polygons, but the *type* of rectangle is still polygon, then this
>  class-changing operation is perfectly type-safe (as I mentioned in an
>  earlier posting).  The add_vertex operation on rectangles modifies the
>  rectangle *in place* to be a pentagon-represented polygon; the
>  interface to the rectangle remains unchanged.  This facility requires
>  that some classes be marked as not defining new types but instead new
>  implementations of existing types.

This is an interesting solution.  However, to clarify a point, it
seems to me that this solution implies that clients of polygon do not
know about the different implementations, but rather the system
somehow chooses the correct implementation, probably based on
constraints defined by the implementation.  If this is true, then
clients of polygon can only declare variables to be of type polygon, and
not of type rectangle.  (In fact, I think it would be a mistake to
call these alternative implementations "classes".)

Multiple implementations support the definition of algorithms of
varying efficiency, but offer no client control over which
implementation is chosen.  (There may be faster algorithms for drawing
rectangles than other polygons, but the client can't say they only
want to consider rectangles.)

Subtyping offers the client finer-grained control over the desired
semantics, but (in strong type-checking languages) restricts the
programmer's flexibility in defining subtypes.  (The client can
restrict a variable to be a rectangle, but now the programmer can't
define add_vertex on polygons.)

I suspect there are uses for both solutions.

--

Barbara Staudt Lerner
Deparment of Computer and Information Science
Lederle Graduate Research Center
University of Massachusetts
Amherst, MA  01003

blerner@cs.umass.edu

dbc@cimage.com (David Caswell) (02/08/91)

.>We know how to add vertices to POLYGONs.  I think it ridiculous to say
.>we do not know how to add vertices to RECTANGLEs.  The problem is,
.>adding a vertex to a RECTANGLE gives us something that is not a
.>RECTANGLE.  
.>

I don't think that we know how to add vertices to all POLYGONs.
Rectangles for example.  I think the solution is to put the 
rectangle methods on polygon and have them assert that the
number of vertices equals 4.  They could be put in another
file or whatever if you're worried about readability.  This 
problem comes up in countless situations.  Unfortunately
there is not a perfect solution.

wright@hsi86.hsi.com (Gary Wright) (02/09/91)

In article <27A9AB0C.4794@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes:
>Inheritance and information hiding issues are orthagonal to assertion
>issues.  A given language may support the former, the latter, both, or
>neither.  Let's keep the discussions clear.

At least in Eiffel, the semantics of inheritance are not orthagonal
to assertions.  An interesting part of this discussion has been what
role assertions (pre-conditions, post-conditions, invariants) have in
an inheritance model and how does that restrict the way a class can
be changed with respect to its parent(s).  I'd like to hear more, not
less, regarding this issue.
-- 
Gary Wright                                                 ...!uunet!hsi!wright
3M Health Information Systems					  wright@hsi.com

cjmchale@cs.tcd.ie (Ciaran McHale) (02/09/91)

Here's my $0.02 worth on the topic concerning rectangles and
polygons.

Some people have pointed out that since rectangle doesn't
support all the operations which might be performed on a
polygon, rectangle should *not* be a subtype of polygon.
(Other approaches were also suggested.) Instead, the graphics
hierarchy should look like:

		graphic (base class)
		  /\
		 /  \
		/    \
	  rectangle  polygon

A problem with this approach is that rectangle is certainly
similar to polygon in many respects and so we would like to
have it derived from polygon so that we can reuse code.

Bascially we're torn between two seemingly conflicting issues:

	1. We can't have rectangle as a subtype of polygon
	   since add_vertex doesn't make sense when applied
	   to a polygon.
	2. We do not want to duplicate code which is common
	   between rectangle and polygon.

One way to solve this dilemma is for the language to have two
hierarchies rather than just one. There would be a TYPE
hierarchy and an IMPLEMENTATION (CODE) hierarchy. Using this
scheme, rectangle and polygon can be siblings in the TYPE
hierarchy, but rectangle could (if the programmer wanted to)
inherit code from polygon in the IMPLEMENTATION heierarchy.


    TYPE HIERARCHY			IMPLEMENTATION HIERARCHY
    --------------			------------------------
	graphic					 graphic
	  /\					    |
	 /  \					    |
	/    \					 polygon
  rectangle  polygon				    |
						    |
						rectangle

Note that the IMPLEMENTATION hierarchy does not have to
mirror the TYPE hierarchy. The IMPLEMENTATION of rectangle
will include the add_vertex operation (inherited from the
IMPLEMENTATION of polygon) but this will be "private" since
it is not specified in rectangle's TYPE.


Ciaran.
-- 
Ciaran McHale		"Verbosity says it all"			      ____
Department of Computer Science, Trinity College, Dublin 2, Ireland.   \  /
Telephone: +353-1-772941 ext 1538	FAX: +353-1-772204	       \/
Telex: 93782 TCD EI			email: cjmchale@cs.tcd.ie

bertrand@eiffel.UUCP (Bertrand Meyer) (02/10/91)

** Note: As Markku Sakkinen requested before, it would be nice
if everyone left the original groups in the Newsgroups line.
Someone, for some reason, had removed comp.lang.eiffel. 
I have put it back. ** end of note.

This is simply to point out that the issue of ``implementation
reuse'' versus ``specification reuse'' has nothing to do
with this whole discussion. The debate (e.g. my original
questions regarding polygons and rectangles) can be
entirely confined, if need be, to the purely mathematical
notion of abstract data types.  We can talk about inheritance
relations as being morphisms in categories, and we don't even
need to refer to computers.

I should add that ``implementation reuse'' is, for
me, a term devoid both of meaning and of interest.
The separation between concept and implementation in software is
an historical evil, not a desirable situation. With a good notation we
can remove the difference. For one thing, this is clearly the
goal of Eiffel. But of course the human tendency to make
things difficult when they should be simple is eternal.

I also know that there is very little chance that anyone
beyond Eiffel users will accept this today. It will
take several years; too many people have been
taught that implementation is dirty and ``specification''
or ``design'' is good. (See the lengthy letter in the January
Communications of the ACM, by a gentleman from the US Coast
Guard, claiming that he represents the trenches of the computing
profession and, among other things, that software engineering has
nothing to do with implementation.)

Of course if one's world is an environment where implementation
is done in Cobol or C, the need for something at a higher level
is obvious. But this is irrelevant to object-oriented
technology, or software engineering for that matter. 

Somewhere in his essays on linguistics, Roman Jakobson
illustrates a point by telling the following story.

A missionary was blaming natives because they always
went everywhere naked. ``Cover your body!'', he would scream.
Once they asked back: ``But you too have an area
of uncovered flesh, right there, haven't you?''.
``Of course'', said the Father, ``but that's different! It's my face''. 
``Well then'', retorted the natives, ``with us, everything is the face''.

I like to think of this as a guide for software construction.
One day we will stop being ashamed of implementation,
not because we will have removed the need for implementation
but for exactly the opposite reason: with us, everything
is the implementation.

-- Bertrand Meyer
bertrand@eiffel.uucp

cjmchale@cs.tcd.ie (Ciaran McHale) (02/11/91)

This discussion has used a concrete example (involving
polygons, rectangles and an add_vertex operation) to
discuss an issue. From the top of my head I can recall
the following approaches being suggested:

	o Having the add_vertex operation in polygon makes
	  that class too specialised to be used as a
	  superclass for rectangle. Reorganise the class
	  hierarchy a bit to solve the problem.
	o The problem can be solved if the language allows
	  an object to change its type at run-time. Thus
	  applying add_vertex to a rectangle changes that
	  object into a (pentagon or a) polygon.
	o Rectangle.add_vertex should be a null operation.
	o Rectangle.add_vertex should raise an exception.
	o Have very fine grain multiple inheritance.
	o Have a read_only interface to each class. The
	  read_only interface for polygon would exclude
	  add_vertex, and this resultant class is then
	  suitable as a superclass for rectangle.

(I'm sure that other approaches were discussed too.) Then I
add in my $0.02 worth suggesting:

	o If a language has separate type and implementation
	  hierarchies then rectangle can reuse code from
	  polygon without having to support add_vertex in
	  its type.

Bertrand Meyer (article <498@eiffel.UUCP>) replies with:
>This is simply to point out that the issue of ``implementation
>reuse'' versus ``specification reuse'' has nothing to do
>with this whole discussion.

Why not? It _is_ an approach to solving the problem at hand.
It may not be the best approach but I don't see why you
are so eager to dismiss it out of hand.

>The separation between concept and implementation in software is
>an historical evil, not a desirable situation. With a good notation we
>can remove the difference.
>[...]
>One day we will stop being ashamed of implementation,
>not because we will have removed the need for implementation
>but for exactly the opposite reason: with us, everything
>is the implementation.

I don't know how (in)correct you are. Only time will
tell. But just because you feel that way does not
mean that others share your beliefs. So until
separation-between-type-and-implementation is widely
accepted to be a bad idea, why not consider it where it
might be useful?


Ciaran.
-- 
Ciaran McHale		"Verbosity says it all"			      ____
Department of Computer Science, Trinity College, Dublin 2, Ireland.   \  /
Telephone: +353-1-772941 ext 1538	FAX: +353-1-772204	       \/
Telex: 93782 TCD EI			email: cjmchale@cs.tcd.ie

weigand@kub.nl (Hans Weigand) (02/11/91)

In article <494@eiffel.UUCP> bertrand@eiffel.UUCP (Bertrand Meyer) writes:
>...
>This does not work, since the new precondition would be stronger (again,
>see above references). It would make it impossible for a client

Perhaps the problem discussed here hinges on an inappropriate 
definition of the precondition. Usually, preconditions are thought
to be *nessecary* conditions, and, moreover, the implicit assumption
is that when all necessary conditions have been specified, these
together are also sufficient. In this conception, preconditions
can be inherited and strenghtened for subtypes.
However, if a precondition is assumed to be sufficient, as Eiffel does,
rather than necessary, then the inheritance runs counter to the
usual line of specification. That is, the programmer first specifies
the supertype, and specifies the preconditions that he deems necessary.
However, when he later comes to subtypes, he typically wants to add
more specific preconditions. In the "sufficiency" approach, the 
programmer should be aware of all subtypes details when he is
designing the supertype, which is not reasonable.
Perhaps you say that the "sufficiency" approach follows from the
contract view, but I doubt. When I make a contract for X, and state
my conditions (the obligations of the client), I do not commit myself
more than needed. If the client wants to use my product Y too (the subtype),
then I would like to be able to negotiate new conditions. These
conditions then add to the first ones.

Reference: 
Wieringa R, H Weigand, JJ Meyer, F Dignum: The Inheritance of
Dynamic and Deontic Integrity Constraints. Annals of Mathematics
and Artificial Intelligence, Nov 1990.
--
Hans Weigand
Tilburg University

paj@mrcu (Paul Johnson) (02/11/91)

In article <1991Feb7.205232.23548@Neon.Stanford.EDU> craig@Neon.Stanford.EDU (Craig D. Chambers) writes:
>In article <BLERNER.91Feb6100509@empire.cs.umass.edu> blerner@empire.cs.umass.edu (Barbara Lerner) writes:

> [Good stuff about the polygon-rectangle problem deleted.  Briefly,
> the idea was that rectangle.add_vertex turns the object into a
> polygon]

In passing, its worth noting that the InterViews solution to this very
problem was similar to what was proposed in the referenced articles.
If you asked for a rectangle, you got a polygon with 4 vertices and
all corners 90 degrees.  This could then be rotated and stretched at
will.

Paul.

-- 
Paul Johnson                               UUCP: <world>!mcvax!ukc!gec-mrc!paj
--------------------------------!-------------------------|-------------------
GEC-Marconi Research is not 	| Telex: 995016 GECRES G  | Tel: +44 245 73331
responsible for my opinions.	| Inet: paj@uk.co.gec-mrc | Fax: +44 245 75244

Bruce_Atherton@mindlink.UUCP (Bruce Atherton) (02/14/91)

I have some thoughts to add on this debate regarding how to deal with a
RECTANGLE and the add_vertex call.

When trying to work these questions out, I usually rely on real world examples,
so please bear with me.  Consider a gardener who is told that to prune plants
you have to cut some branches off.  He is given the task of pruning a garden,
so he goes to each plant and does so.

What does he do when faced with a cactus?  It has no discernible branches, yet
it is clearly a plant.  He could cut away at it anyway, but this is stupid
behaviour.  He could just ignore it, but if the cactus required special care
instead of pruning, this would not be smart either.  The best solution would be
for the gardener to report to whoever gave him the task that he could not prune
a cactus.

This seems to be analogous to the RECTANGLE-add_vertex debate.  The correct
action would be to report an error.

Bertrand Meyer pointed out that this would alter the terms of the contract, but
I don't think this is necessarily correct.  It could be a fundamental part of
the language that a contract can be breached (to continue the metaphor).  The
action taken when an object is in breach of contract is up to the object that
is contracting the services.

I don't know whether ALL contracts should be breachable.  After all, there are
some things that are so fundamental to our understanding of a polygon that we
can't imagine a polygon without them.  On the other hand, just because we can't
imagine one doesn't mean that someone who reuses the class can't.

I am not a language designer, nor have I paid much attention to such details in
the past.  I am just an end user of these languages.  It may well be that
allowing a contract to be breached invalidates some model that is used in
object-oriented language design.  If it does, though, in my opinion there is
something wrong with the model.  It is often required that a subclass eliminate
a property of the more general class. A language which does not provide this
facility is not meeting my needs as a user.  And isn't that what it is all
about?
--
UBC Faculty of Law Artificial Intelligence Research Project
Bruce_Atherton@mindlink.UUCP or| "You fell for the second most famous blunder.
uunet!van-bc!rsoft!mindlink!a35| The most famous is never to get involved in a
                                 land war in Asia." - The Princess Bride

simons@tetrauk.UUCP (Simon Shaw) (02/18/91)

In article <4810@mindlink.UUCP> Bruce_Atherton@mindlink.UUCP (Bruce Atherton) writes:
>I have some thoughts to add on this debate regarding how to deal with a
>RECTANGLE and the add_vertex call.
>...
> The correct action would be to report an error.
>
>Bertrand Meyer pointed out that this would alter the terms of the contract, but
>I don't think this is necessarily correct.  It could be a fundamental part of
>the language that a contract can be breached (to continue the metaphor).  The
>action taken when an object is in breach of contract is up to the object that
>is contracting the services.
>
>                             ... It is often required that a subclass eliminate
>a property of the more general class.

I have been lurking here for some time, and am very keen that a
satisfactory answer should be found to the inheritance debate.

The above seems to me to hit the central idea of the discussion, i.e.

1. Inheritance is about specialisation.

2. Specialisation is often a _restriction_ of the range of allowed
circumstances.

3. Ergo, not all the features of a parent class can necessarily be
implemented on a child object.

The strongest refutation of this line of argument seems to be the one
which says "If this problem happens, you have got your class design wrong",
(e.g. the solution of FIXED_POLYGON v. VARIABLE_POLYGON) and the subsequent
discussion concentrated on how to find the error (assertions, static checks).

But - can a change in class design be _proved_ to be possible in all cases,
(or proved to be impossible in any case, which is the same thing).

If I've missed something important, flame away !

--
Simon Shaw ; simons@tetrauk.uucp ; net.lurker

jnw@shades.cis.ufl.edu (Joseph N. Wilson) (02/20/91)

I was directed to this discussion (indirectly) by Chris Clifton of Princeton.

In article <1101@tetrauk.UUCP>, simons@tetrauk.UUCP (Simon Shaw) writes:
 [...]
|> 1. Inheritance is about specialisation.
|> 
|> 2. Specialisation is often a _restriction_ of the range of allowed
|> circumstances.
|> 
|> 3. Ergo, not all the features of a parent class can necessarily be
|> implemented on a child object.
|> 
 [...]
|> Simon Shaw ; simons@tetrauk.uucp ; net.lurker

This sort of problem is evident even in languages with no explicit inheritance
mechanism.  Consider integer subranges of Pascal.  Addition of two values
belonging to some subrange a..b does not necessarily yield a value in a..b.
It is not reasonable to assume that all properties of an algebra will hold
for a similar subset algebra inheriting values and operations.

joe wilson
(jnw@cis.ufl.edu)

ogden@seal.cis.ohio-state.edu (William F Ogden) (02/20/91)

Simon Shaw writes:
   ...
>I have been lurking here for some time, and am very keen that a
>satisfactory answer should be found to the inheritance debate.
   ...

Doesn't it strike anyone that there really isn't a satisfactory answer
to the polygon/rectangle inheritance problem. If inheritance weren't a
central tenet of the Object Oriented Programming dogma, but just an
ordinary idea, we could take a detached scientific view and discard it as
just another initially attractive notion that when elaborated in full
generality proves bankrupt.

/Bill

ian@syacus.acus.oz.au (Ian Joyner) (02/25/91)

sakkinen@tukki.jyu.fi (Markku Sakkinen) writes:
>In any case, in typical OOPL's (and this goes as well for C++ as for
>Smalltalk and Eiffel) a Rectangle class should not inherit any kind
>of _concrete_ Polygon class:  the baggage would include not only

You could consider a rectange as a specialised parallelogram. In this
case the rectangles invariant checks the added restriction, that the
rectangles angles are all 90 deg, whereas the parallelogram must only
check the opposite angles are equal, and they add up to 360 deg.
However, a parallelogram is a concrete class from which rectangle
can inherit, as all rectangles are parallelograms.

A square may multiply inherit from a rectangle and a rhombus. In this
case it does not need to extend the invariant, as the essential properties
of all sides being equal are inherited from rhombus, and all angles being
90 deg are inherited from rectangle.
-- 
Ian Joyner                                     ACSNet: ian@syacus.oz
ACUS (Australian Centre for Unisys Software)   DNS:  ian@syacus.oz.au
Tel 61-2-390 1328      Fax 61-2-390 1391       UUCP: ...uunet!munnari!syacus.oz