Newsgroups: comp.lang.smalltalk,comp.lang.c++,comp.lang.java,comp.object
Path: cantaloupe.srv.cs.cmu.edu!bb3.andrew.cmu.edu!newsfeed.pitt.edu!scramble.lm.com!news.math.psu.edu!news.cse.psu.edu!uwm.edu!math.ohio-state.edu!cs.utexas.edu!howland.reston.ans.net!EU.net!usenet2.news.uk.psi.net!uknet!usenet1.news.uk.psi.net!uknet!psinntp!psinntp!psinntp!merlin.hgc.edu!jcm
From: jcm@hgc.edu (James McKim)
Subject: Re: Dynamic vs. static type checking
Message-ID: <1996Jul24.144026.9035@merlin.hgc.edu>
Sender: usenet@merlin.hgc.edu (Action News Central)
Organization: The Hartford Graduate Center
References: <rmartin-1607961107120001@hgc.edu> <31f37f31.0@info4.uni-rostock.de>
Date: Wed, 24 Jul 1996 14:40:26 GMT
Lines: 193
Xref: glinda.oz.cs.cmu.edu comp.lang.smalltalk:41285 comp.lang.c++:201937 comp.lang.java:70896 comp.object:52601

In article <31f37f31.0@info4.uni-rostock.de> schlegel@Informatik.Uni-Rostock.de writes:
>In article <1996Jul19.162823.19190@merlin.hgc.edu>, James McKim wrote:
>>
>>The real issue here is an old one in this group. Should inheritance
>>be used for subtyping (inheriting interface, by which I mean
>>both the syntax of a routine _and_ its specification) or for
>>subclassing (sharing implementation) as well? Robert is arguing
>>for subtyping only, and points out that while C++ (and Eiffel and a host
>>of other languages) allow descendants to hide features that their
>>ancestors made public, this is not a good idea in practice. Juergen
>>is basically arguing that it seems silly to rewrite the code for Square
>>from scratch when most of what you need is already there in Rectangle
>>and the most direct way for Square to get at it is to inherit it.
>>It is then up to the users of these classes not to treat an instance of Square
>>as a kind of Rectangle. This means no assignments of the form rect := squ
>>and no sending Square instances to a routine that is expecting a
>>Rectangle. 
>
>No, this was not my point. I answered to a posting that denied  that the
>'real world' shows multiple inheritance. The point I was trying to make
>was that 'inheritance' is not the same on all levels. The Square vs. Rect
>example was part of my argument that classification by contraining cannot 
>safely be mapped into class hierarchies for programming languages. These
>classifications (as in biology, or mathematics) are based on immutable
>characteristics, so they do not have to care for theie elements being
>updated.

Oops, didn't mean to misrepresent you, sorry.

>
>As for the argument, I also disagree: of course you can subclass Square
>from Rectangle, as long as you do not provide methods to modify the
>current object (instead, return a new one with the desired state),

I disagree that we disagree :-). As reproduced by you below I pointed
out another way in which Square can be viewed as a subtype of Rectangle,
but your way makes perfect sense as well.

>since otherwise you have to deal with objects that change their class:
>- give up static type checking, or
>- do not use the class hierarchy to define substitutability
> 
>The full set of choices is: Only three of the following four 
>items can be safely used together:
>* class hierarchy by constrains
>* substitutability along the class hierarchy
>* mutable objects
>* static type checking

Sure they can. You do the first three and I'll write the static type
checker so that it rejects everything. What could be safer :-)?
Seriously, I believe you can have all four, but you will need
a pessimistic type checker (though not as pessimistic as the
one I just suggested!). Part of the type checker's job will
be to say just when you have substitutability along the class
hierarchy.

Aside, type checkers are always somewhat pessimistic, so IMHO the
real issue is whether you can support all four of the above and
still have sufficient flexibility to get the job done.


[..]

>
>
>>If the specification of set_width in Rectangle goes as follows:
>>
>>  set_width( w : INTEGER )
>>  require w > 0
>>  ensure
>>    width = w
>>    height = old height
>>
>>Then it is clear that SQUARE cannot satisfy the semantics of Rectangle
>>and so cannot be a subtype. The designers of class Square can then
>>either 
>>
>>  1. Decide that Square will not inherit from Rectangle (Robert's
>>   approach).
>>
>>  2. Decide that Square will inherit from Rectangle but hide the
>>   offending features (Juergen's approach). In this case of course
>>   the documentation of Square should clearly state that it is not
>>   to be used where a Rectangle is expected.
>

[..]

>
>
>>OTOH, if the designers of Rectangle are a bit more flexible/prescient
>>they might define set_width as follows:
>>
>>  set_width( w : INTEGER )
>>  require w > 0
>>  ensure
>>    width = w
>>    -- default is height = old height but descendants may redefine
>>
>>
>>Now the designers of SQUARE _would_ be able to treat it as a subtype
>>of RECTANGLE as they could define set_width as:
>>
>>  set_width( w : INTEGER )
>>  require w > 0
>>  ensure
>>    width = w
>>    height = w
>
>Well, I don't feel comfortable with this, since I always have some
>kind of closed-world-assumption when reading pre/post-conditions,
>namely that items that are not mentioned in the conditions stay
>unchanged, or in other words, that _all_ side effects of a routine
>are mentioned in its postcondition. There was a thread some time
>ago about the different approaches to read such conditions: whether
>they are minimal or maximal contracts (could have been a discussion
>in comp.lang.eiffel). I tend to read them as maximal, whereas your
>suggestion requires them to be minimal.

I have found the no-change assumption less than adequate in practice.

  1) if an assertion is omitted there is no way for the reader to be
   sure that the omission is intentional

  2) The compiler cannot generate code to catch such errors of omission,
   or even, in the general case, generate code to check that nothing
   other than what was specified actually changed.

  3) Does the fact that I've omitted 'area' from the postcondition imply
   that 'area' has not changed? Same question for 'perimeter', 'diagonal'
   etc.

  4) If I add a query to the class I must re-examine every command to
   see whether the command changes the value of that query.

  5) As illustrated above, it's not clear what descendants who redefine
   a routine can and cannot change.

What I do instead is denote a small subset of queries as a "specification
set" and then

   a) Specify all other queries in terms of those in the specification set.

   b) Describe as precisely as possible the effect of each command on each
   query in the specification set. The effect may be no change, but I am
   obligated to document it.

This approach addresses, though by no means completely resolves, all of
the above issues.

In the case of RECTANGLE, a likely specification set would be 'width', 'height',
'center', and 'angle'. Now 'set_width' may be fully define as 

  set_width( w : INTEGER )
  require w > 0
  ensure
    width = w
    height = old height
    center = -- appropriate translation of old center
        (don't have my geometry text handy :-))
    angle = old angle

and 'area' as

  area : REAL is
  ensure
    Result = width * height

>
>
>
>Regards,
>  Juergen Schlegelmilch
>
>-- 
>+-----------------------------------------------------------------------------+
> Dipl.-Inf. Juergen Schlegelmilch                 University of Rostock
> email: schlegel@Informatik.Uni-Rostock.de        Computer Science Department
> http://www.informatik.uni-rostock.de/~schlegel   Database Research Group 
> Tel: ++49 381 498 3402                           18051 Rostock 
> Fax: ++49 381 498 3404                           Germany
>+-----------------------------------------------------------------------------+
>


Regards,
-- Jim
-- 

*------------------------------------------------------------------------------*
Jim McKim  (860)-548-2458     Teachers affect eternity. They can never tell 
Internet:  jcm@hgc.edu        where their influence stops.
