Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Is a square a rectangle?

12 views
Skip to first unread message

Lorin

unread,
Oct 28, 2000, 4:20:45 PM10/28/00
to
I've been reading Object-Oriented Software Construction by Meyer, and I
reached the chapter where he talks about different kinds of inheritance
(Ch 24). He mentions "restriction inheritenace" as a valid use of
inheritance, giving an example of a Square class inheriting from a
Rectangle class.

This really surprised me. My OO experience is limited to C++ (with a
little Java), and in Cline's C++ FAQ, he makes it very clear that a
Square is NOT a Rectangle in terms of the inheritance hierarchy,
because you could do something like:

Square *s = ...;

Rectangle *r = s;
r->set_sides(10,20); // BAD! Set the two sides to different lengths


So, who's right? Is it a language thing (i.e. you can't get away with
this in C++, but in Eiffel there are mechanisms to avoid this problem
like assertions)? Is this an unresolved issue?

Lorin


Sent via Deja.com http://www.deja.com/
Before you buy.

matt newell

unread,
Oct 28, 2000, 4:46:18 PM10/28/00
to
Lorin wrote:

First of all, I am sure that the whole square inherets rectangle is just an
example that wouldn't really be used in real life, however it doesn't mean
that it can't be done;

class Rect {
int x, y;
public:
virtual void setGeometry( int gx, int gy){
x=gx;
y=gy;
}
};

class Square: public Rect{
public:
virtual void setGeometry( int gx, int gy){
/*Since we have overloaded the function, we can do whatever we want with the
data, maybe will will say that only gx is used */
x = y = gx;
}
};

Now everybody is happy. Square inherets Rect, and it maintains equal sides by
overloading the setGeometry function :)

Phlip

unread,
Oct 28, 2000, 4:50:19 PM10/28/00
to
"Lorin" escribió:

> So, who's right? Is it a language thing (i.e. you can't get away with
> this in C++, but in Eiffel there are mechanisms to avoid this problem
> like assertions)? Is this an unresolved issue?

The issue will remain unresolved - as any code construction issue - until
you actually write a program that needs squares and rectangles. You'l
probably find you have only one class, a rectangle, and squares will be
incidental instances of these. Or you may find you have square and rectangle
classes that inherit a common shape class.

But you can _never_ answer a design question without context. You'l
typically find that the context will then dictate the design without
requiring any philosophy at all.

Use what's left of www.deja.com to research, on this newsgroup, copious
discussions of "circle and ellipse". This is the same argument under
different props.

--
Phlip
======= http://users.deltanet.com/~tegan/home.html =======


Phlip

unread,
Oct 28, 2000, 4:53:06 PM10/28/00
to
"matt newell" escribió:

> Now everybody is happy. Square inherets Rect, and it maintains equal
sides by
> overloading the setGeometry function :)

Barbara Liskov would not be happy :(

Andy Watson

unread,
Oct 28, 2000, 7:38:35 PM10/28/00
to
From my point of view I think that both squares and rectangles are
both instance objects where as shape would be an abstract type that I
would add to provide the protocol for both.

I wouldn't have square inheriting from rectangle simply because I cant
see any extra protocol that square would add to or remove from
rectangle.

Effectively this puts square and rectangle at the same level of
inheritance. Both square and rectangle place different constraints on
shape.

Andy.

On Sat, 28 Oct 2000 20:20:45 GMT, Lorin <hawke...@my-deja.com>
wrote:

Peter van Rooijen

unread,
Oct 28, 2000, 7:05:52 PM10/28/00
to
> This really surprised me. My OO experience is limited to C++ (with a
> little Java), and in Cline's C++ FAQ, he makes it very clear that a
> Square is NOT a Rectangle in terms of the inheritance hierarchy,
> because you could do something like:
>
> Square *s = ...;
>
> Rectangle *r = s;
> r->set_sides(10,20); // BAD! Set the two sides to different lengths

Of course that implies a particular meaning of the Rectangle concept,
i.e. that its size can be set after creation. You have to answer the
question what you mean by Rectangle and Square before you can say
whether one is compatible with the other.

> So, who's right? Is it a language thing (i.e. you can't get away with
> this in C++, but in Eiffel there are mechanisms to avoid this problem
> like assertions)? Is this an unresolved issue?

Consider this: there is no right or wrong. Inheritance is/does what it
is/does, and what it is/does depends on the language in which you use
it. After that, what it is/does determines what you can use it for.

Regards,
--

Peter van Rooijen
e-mail pe...@vanrooijen.com
fax, voice mail +31 20 866 2895
Camp Smalltalk www.campsmalltalk.com


l...@cc.gatech.edu

unread,
Oct 28, 2000, 8:36:22 PM10/28/00
to
"Peter van Rooijen" <pe...@vanrooijen.com> writes:
> Of course that implies a particular meaning of the Rectangle concept,
> i.e. that its size can be set after creation. You have to answer the
> question what you mean by Rectangle and Square before you can say
> whether one is compatible with the other.

This is key. In fact, a "rectangle" from mathematics doesn't change.
A rectangle that can change over time is a different kind of beast.

Neither MutableRectangle nor MutableSquare is a subtype of the other.


-Lex

Richard Smol

unread,
Oct 29, 2000, 3:36:36 AM10/29/00
to

Lorin wrote:
>
> I've been reading Object-Oriented Software Construction by Meyer, and I
> reached the chapter where he talks about different kinds of inheritance
> (Ch 24). He mentions "restriction inheritenace" as a valid use of
> inheritance, giving an example of a Square class inheriting from a
> Rectangle class.
>
> This really surprised me. My OO experience is limited to C++ (with a
> little Java), and in Cline's C++ FAQ, he makes it very clear that a
> Square is NOT a Rectangle in terms of the inheritance hierarchy,
> because you could do something like:

A Square is a Rectangle with sides of equal length. That's it, really.
I would not make a seperate Square class. Maybe you could even add
an "isSquare" method to see if a Rectangle is indeed a Square (although
a client of the class could simply deduce that itself). Once you change
it's height or width though, the thing is simply not a Square anymore.

Maybe you would like to use different formulas for caclulating various
properties of a Square or Rectangle. In that case, you might let an
object change those formulas at run time, depending on if it's a Square
or Rectangle at the time.

Greetz,

RS

dmitr...@my-deja.com

unread,
Oct 29, 2000, 6:50:45 AM10/29/00
to
In article <8tfceq$5ec$1...@nnrp1.deja.com>,

The famous Circle-Ellipse problem again! (:-))

One may not make Square a subtype of Rectangle in C++ (because of LSP
misuse), which does not mean that this is impossible in principle. One
can imagine a language where Rectangle and Square would have diffrent
representations and still Rectangle would be a subtype of Square.

In such language

r->set_sides(10,20);

would be legal and mean:

1. create a temp Rectangle intialized by the value of *r;
2. apply set_sides(10,20) to the temp;
3. store the value of temp into *r, which would raise a suitable
exception in the Rectangle-to-Square conversion function.

So both are correct, Meyer in general (:-)), C++ FAQ as expected for
C++ (:-()

--
Regards,
Dmitry Kazakov

Rainer Joswig

unread,
Oct 29, 2000, 7:38:17 AM10/29/00
to
In article <39fb3...@news.pacifier.com>, matt newell
<new...@proaxis.com> wrote:

> First of all, I am sure that the whole square inherets rectangle is just an
> example that wouldn't really be used in real life, however it doesn't mean
> that it can't be done;
>
> class Rect {
> int x, y;
> public:
> virtual void setGeometry( int gx, int gy){
> x=gx;
> y=gy;
> }
> };
>
> class Square: public Rect{
> public:
> virtual void setGeometry( int gx, int gy){
> /*Since we have overloaded the function, we can do whatever we want with the
> data, maybe will will say that only gx is used */
> x = y = gx;
> }
> };
>
> Now everybody is happy. Square inherets Rect, and it maintains equal sides by
> overloading the setGeometry function :)

Why not go a bit further, if we detect that things change,
we change things. ;-)

This is a bit Common Lisp code:

(defclass rectangle ()
((x :type integer :initarg :x)
(y :type integer :initarg :y)))

(defclass square (rectangle) ; square is a subclass of rectangle
())

(defmethod set-geometry ((a-rectangle rectangle) new-x new-y)
(with-slots (x y) a-rectangle
(setf x new-x y new-y)))

; Common Lisp let's you change the class of an object:

(defmethod set-geometry :after ((a-rectangle rectangle) x y)
(when (= x y)
(change-class a-rectangle 'square)))

(defmethod set-geometry :after ((a-square square) x y)
(unless (= x y)
(change-class a-square 'rectangle)))

; Example:

(let ((a-rectangle (make-instance 'rectangle :x 10 :y 12)))
(print (class-of a-rectangle)) ; it's a rectangle
(set-geometry a-rectangle 5 5) ; same sides
(print (class-of a-rectangle)) ; oops, now it's a square
(set-geometry a-rectangle 5 10) ; different sides
(print (class-of a-rectangle))) ; oops, now it's a rectangle again

; prints:

#<STANDARD-CLASS RECTANGLE>
#<STANDARD-CLASS SQUARE>
#<STANDARD-CLASS RECTANGLE>

So the rectangle is a square and back.

--
Rainer Joswig, Hamburg, Germany
Email: mailto:jos...@corporate-world.lisp.de
Web: http://corporate-world.lisp.de/

Robert C. Martin

unread,
Oct 29, 2000, 9:26:43 AM10/29/00
to
Deriving Square from Rectangle using the kind of inheritance found in Java,
C++, Python, etc, is usually a bad thing. It violates a principles known as
"The Liskov Substitution Principle" (LSP) and lays the seeds of nasty
if/else and type-case statements in client code.

Bertrand's discussion about the N different kinds of inheritance is
interesting, but a bit theoretical in the context of today's common
languages.


--

Robert C. Martin | "Uncle Bob" | Software Consultants
Object Mentor Inc. | rma...@objectmentor.com | We'll help you get
PO Box 5757 | Tel: (800) 338-6716 | your projects done.
565 Lakeview Pkwy | Fax: (847) 573-1658 | www.objectmentor.com
Suite 135 | | www.XProgramming.com
Vernon Hills, IL, | Training and Mentoring | www.junit.org
60061 | OO, XP, Java, C++, Python|

"One of the great commandments of science is:
'Mistrust arguments from authority.'" -- Carl Sagan


"Lorin" <hawke...@my-deja.com> wrote in message
news:8tfceq$5ec$1...@nnrp1.deja.com...

Roger L. Cauvin

unread,
Oct 29, 2000, 9:31:14 PM10/29/00
to
Lorin,

This question, and questions like it (circle/ellipse) have been asked and
answered many times on this and other newsgroups. I have extracted the
following passages from the replies to your message. I believe they
summarize some of the most important points made over the years on this
issue.

"Peter van Rooijen" <pe...@vanrooijen.com> writes:
> Of course that implies a particular meaning of the
> Rectangle concept, i.e. that its size can be set after
> creation. You have to answer the question what you
> mean by Rectangle and Square before you can say
> whether one is compatible with the other.

<l...@cc.gatech.edu> writes


> This is key. In fact, a "rectangle" from mathematics doesn't change.
> A rectangle that can change over time is a different kind of beast.
>
> Neither MutableRectangle nor MutableSquare is a subtype of the other.

"Robert C. Martin" <rma...@objectmentor.com> writes


> Deriving Square from Rectangle using the kind of
> inheritance found in Java, C++, Python, etc, is

> USUALLY [my emphasis] a bad thing. It violates


> a principles known as "The Liskov Substitution
> Principle" (LSP) and lays the seeds of nasty if/else
> and type-case statements in client code.

"Phlip" <n...@spam.com> writes


> But you can _never_ answer a design question without

> context. You'll typically find that the context will then


> dictate the design without requiring any philosophy at all.

Having read these pearls of wisdom, I believe the participants in this
newsgroup should be proud.

Roger

--
Roger L. Cauvin
rca...@homemail.com
http://www.thegym.net/rcauvin


MoJieZhong--?a?~{(9~}?~{(2~}

unread,
Oct 29, 2000, 11:09:09 PM10/29/00
to

"Lorin" <hawke...@my-deja.com> wrote in message
news:8tfceq$5ec$1...@nnrp1.deja.com...

Hello:
I am learning this book too. And I think the restriction is from the
conceptual domain .And it can be implemented in different form ( like
precondition or exception or even just in document.). New resconstriction
which introduced by overloaded method only means the change of contract. Of
course it is not often , But you can find example from JavaAPI. Some technic
for this kinds of change can be found in preChapter(I forget which).
In swing , java.swing.JFrame extends java.awt.Frame but the add(Component
com); method make more restriction .
if you do this :
Frame f = new JFrame();
f.add(new JButton());

you will get a exception.

Hope this help . And sorry for poor english.

;-> MoJieZhong


Panu Viljamaa

unread,
Oct 30, 2000, 9:06:26 PM10/30/00
to
Lorin wrote:

> ... My OO experience is limited to C++ (with a little Java), and in


> Cline's C++ FAQ, he makes it very clear that a Square is NOT a Rectangle
> in terms of the inheritance hierarchy, because you could do something
> like:

I think this is an artificial example. In philosophy we can discuss whether
a Square is a Rectangle if we give precise definitions of what is a
Rectange and what is a Square, and what it means to "be a".

In programming we are interested in creating software that works. If I was
to create a program dealing with graphical objects, I would probably create
a class called Rectangle but probably not a class called 'Square'. If a
user wanted to create rectangles with equal width and height it would be
easy to provide a menu-option for this, perhaps labelled 'Rectangle'. I
could also create a method #isSquare for the class Rectangle. Simple huh.
'Squareness' is a property of Rectangles, not a class of its own.

- Panu

Robert C. Martin

unread,
Oct 30, 2000, 10:03:09 PM10/30/00
to

"Panu Viljamaa" <pa...@way.com> wrote in message
news:39FE2922...@way.com...

When you have to make a 100,000,000 of them, you'll be glad you put them in
a class of their own.

dmitr...@my-deja.com

unread,
Oct 31, 2000, 4:04:08 AM10/31/00
to
In article <39FE2922...@way.com>,

Panu Viljamaa <pa...@way.com> wrote:
> Lorin wrote:
>
> > ... My OO experience is limited to C++ (with a little Java), and in
> > Cline's C++ FAQ, he makes it very clear that a Square is NOT a
Rectangle
> > in terms of the inheritance hierarchy, because you could do
something
> > like:
>
> I think this is an artificial example. In philosophy we can discuss
whether
> a Square is a Rectangle if we give precise definitions of what is a
> Rectange and what is a Square, and what it means to "be a".

It is not an artifical example and "be a" has a pretty well defined
meaning: "is a subtype". The example just shows that "naive" LSP does
not work in some cases.

--
Regards,
Dmitry Kazakov

Malcolm Jenner

unread,
Oct 31, 2000, 10:52:12 AM10/31/00
to
In article <8tfceq$5ec$1...@nnrp1.deja.com>, hawke...@my-deja.com says...

>
>I've been reading Object-Oriented Software Construction by Meyer, and I
>reached the chapter where he talks about different kinds of inheritance
>(Ch 24). He mentions "restriction inheritenace" as a valid use of
>inheritance, giving an example of a Square class inheriting from a
>Rectangle class.
>

Mathematically speaking a square is a special case of both a rectangle and a
rhombus. If you are trying to model the mathematics then you should have
square inheriting from both rectangle and rhombus, both of which inherit from
parallelogram. The rectangle also inherits from isosceles trapezium, which in
turn inherits from trapezium. The rhombus also inherits from kite. Both
kite and trapezium inherit from quadrilateral.

Malcolm S Jenner
Senior Lecturer
School of Computing & IT
University of Wolverhampton

Roy MacLean

unread,
Nov 1, 2000, 5:26:57 AM11/1/00
to
In article <8tfceq$5ec$1...@nnrp1.deja.com>,
Lorin <hawke...@my-deja.com> wrote:

[discussion of SQUARE inherit RECTANGLE from OOSC]

I'm sure that Bertrand doesn't intend this as an example of serious
software design, merely as a brief text-book illustration which doesn't
require a lot of background requirements.

If one were seriously designing a graphics tool, this kind of taxonomic
problem is a sure sign that you should move towards a more dynamically
typed solution.

For example, one could have a general POLYGON class that (1) supports
operations to add sides, change side lengths, change angles, change
aspect ratio, change position, and so on; and (2) a dynamically
modifiable set of constraints on such changes. So I could then draw a
rectangular POLYGON, fiddle around with it, then decide to fix its
number of sides, orientation and aspect ratio, but not its size or
position. If I happened to want a square, I would simply fix its aspect
ratio to be 1.

The design problem is then to define the repertoire of constraints, and
work out how they interact. This seems a more useful approach than
trying to provide a fixed taxonomy of shapes.
(Aside: Bertrand's 'Linnaean' taxonomy of basic classes in EiffelBase
is valid because it's in the well-understood domain of Computer
Science, not the poorly-understood domain of user requirements).

Regards
Roy MacLean
--
OM Technology
London
(roy.m...@REMOVE.omgroup.com)

Graham Perkins

unread,
Nov 1, 2000, 12:22:07 PM11/1/00
to
Sometimes it is, sometimes it isn't.

And don't forget that the question itself is less
relevant in the context of dynamic typed programming
languages.

Graham Perkins

unread,
Nov 1, 2000, 12:25:13 PM11/1/00
to
> One may not make Square a subtype of Rectangle in C++
> (because of LSP misuse),

If you have dynamic type system, there is no LSP to misuse.
Even in static type system, a Rectangle without a method
to alter its dimensions independently can legitimately be
subclassed into Square.

JDD

unread,
Nov 1, 2000, 11:38:13 AM11/1/00
to

Malcolm Jenner wrote:

The purpose of inheritance is to create derived classes whose objects are
substitutable for objects of the base class. Depending on the specification of the
base class, inheritance that seems correct in a mathematical sense may or may not
be proper in an object-oriented design sense. If you are using the term "inherit"
strictly in a mathematical "is-a-special-kind-of" sense to illustrate mathematical
relationships, then fine. However, even though a square is-a-special-kind-of
rectangle in a mathematical sense, it does not necessarily follow that class Square
should inherit from class Rectangle. The appropriateness of this depends on the
specification of Rectangle. Several previous posts have pointed out why this may
be improper. I do not disagree with your mathematical analysis, but suspect that
many readers here may have jumped to the wrong conclusion.

And a note to the OP: Cline, in the C++ FAQ, does *not* say that "a Square is NOT
a Rectangle". He only suggests that it *may* not be. It all depends on the
specification of Rectangle, and whether a Square can be substitutable for the
Rectangle as specified.


Graham Perkins

unread,
Nov 1, 2000, 12:59:02 PM11/1/00
to
> to create a program dealing with graphical objects, I would probably create
> a class called Rectangle but probably not a class called 'Square'. If a
> user wanted to create rectangles with equal width and height it would be
> easy to provide a menu-option for this, perhaps labelled 'Rectangle'. I
> could also create a method #isSquare for the class Rectangle. Simple huh.
> 'Squareness' is a property of Rectangles, not a class of its own.

also rectangularity is a property of parallelograms
parrallelogrammic is a property of trapezoids
trapezoidality is a property of rhombi
rhomboidicity is a property of polygons
polygonity is a property of sets of connected points

Great! All shapes are instances of the same class!

Owen Rees

unread,
Nov 1, 2000, 8:01:23 PM11/1/00
to
On Tue, 31 Oct 2000 09:04:08 GMT, dmitr...@my-deja.com wrote:

>It is not an artifical example and "be a" has a pretty well defined
>meaning: "is a subtype". The example just shows that "naive" LSP does
>not work in some cases.

It seems to me that the problem is not with LSP, but in using the term
"a square" where we mean "an object that can represent any square" and
similarly for rectangle. If we ask "is an object that can represent
any square an object that can represent any rectangle?", I would
suggest that most people would consider this obviously false.

In our shorthand terminology we ask "is a square a rectangle?" setting
up the trap of assuming properties of other things that have those
names. A square may be a rectangle in geometry, but that has no
bearing on classes we may have chosen to call "Rectangle" and "Square"
in a program unless we construct those classes so as to have the
relevant properties of the geometric figures.

--
Owen Rees - its just me writing this, see
<http://www.users.waitrose.com/~owenrees/index.html#disclaimer>

Panu Viljamaa

unread,
Nov 1, 2000, 11:21:48 PM11/1/00
to
JDD wrote:

> The purpose of inheritance is to create derived classes whose objects are

> substitutable for objects of the base class. ....

In my view 'inheritance' is a property of some programming languages, which you can use
for what ever purpose it seems to be helpful for.

For instance in Smalltalk it is common to use it for (fast) implementation by
differentiation, not necessarily to create 'substitutable objects'.

-Panu


Mark van Gulik

unread,
Nov 1, 2000, 11:49:34 PM11/1/00
to
In article <3A0059E6...@dmu.ac.uk> , Graham Perkins <g...@dmu.ac.uk>
wrote:


Ah, the birth of Topology!

p.koot...@remove.ieee.org

unread,
Nov 2, 2000, 4:36:02 AM11/2/00
to

And the advent of topologists: people who can't tell their doughnuts
from their tea-mugs.

Ciao,

Peter K.

FTHI: :-)

--
Peter J. Kootsookos Wb: www.clubi.ie/PeterK
"Here comes the future and you can't run from it,
If you've got a blacklist I want to be on it"
- 'Waiting for the great leap forwards', Billy Bragg

dmitr...@my-deja.com

unread,
Nov 2, 2000, 5:34:10 AM11/2/00
to
In article <3A0051F9...@dmu.ac.uk>,

Yes.

However, if you want to have different representations for squares and
rectangles, or an ability to resize a square using the rectangle's
method, you should drop LSP.

dmitr...@my-deja.com

unread,
Nov 2, 2000, 6:34:34 AM11/2/00
to
In article <3a00abd6...@news.waitrose.com>,

owen...@waitrose.deletethis.com (Owen Rees) wrote:
> On Tue, 31 Oct 2000 09:04:08 GMT, dmitr...@my-deja.com wrote:
>
> >It is not an artifical example and "be a" has a pretty well defined
> >meaning: "is a subtype". The example just shows that "naive" LSP does
> >not work in some cases.
>
> It seems to me that the problem is not with LSP, but in using the term
> "a square" where we mean "an object that can represent any square" and
> similarly for rectangle. If we ask "is an object that can represent
> any square an object that can represent any rectangle?", I would
> suggest that most people would consider this obviously false.

Absolutely. And what LSP does? It tries to enforce any square variable
be able to represent any rectangle, which is wrong.

> In our shorthand terminology we ask "is a square a rectangle?" setting
> up the trap of assuming properties of other things that have those
> names. A square may be a rectangle in geometry, but that has no
> bearing on classes we may have chosen to call "Rectangle" and "Square"
> in a program unless we construct those classes so as to have the
> relevant properties of the geometric figures.

I would formulate it as follows:

1. Mathematically "square is a rectangle" means that any square belongs
to the set of all rectangles.

2. In a programming language, "square is a rectangle" means that the
type square is a subtype of the type rectangle.

Now, the question is, can 1. be modelled using 2.?

The answer is there are cases when it is impossible if LSP is used.

JDD

unread,
Nov 2, 2000, 9:03:29 AM11/2/00
to

Panu Viljamaa wrote:

Thanks for your response. Of course you are right that inheritance can be used to share
implementation without the need for substitutability. In C++, for example, one could
properly use private or protected inheritance to accomplish this. This, though, is not the
is-a kind of inheritance that is suggested by the question. Perhaps I should have said
"The purpose of is-a inheritance..." or "The purpose of what in C++ is called public
inheritance...".

So....Can a Square be implemented by differentiation from a Rectangle? Yes. Is-a Square a
Rectangle? Depends on the specification of Rectangle.

John

Danil

unread,
Nov 2, 2000, 1:52:47 PM11/2/00
to
In article <3a00abd6...@news.waitrose.com>,
owen...@waitrose.deletethis.com says...

> On Tue, 31 Oct 2000 09:04:08 GMT, dmitr...@my-deja.com wrote:
>
> >It is not an artifical example and "be a" has a pretty well defined
> >meaning: "is a subtype". The example just shows that "naive" LSP does
> >not work in some cases.
>
> It seems to me that the problem is not with LSP, but in using the term
> "a square" where we mean "an object that can represent any square" and
> similarly for rectangle. If we ask "is an object that can represent
> any square an object that can represent any rectangle?", I would
> suggest that most people would consider this obviously false.

Perhaps I'm naive, but the square/rectangle debate has long seemed pretty
straight forward to me.

Put simply, the conflict is simply one of "objects have properties"
versus "objects provide services".

The choice of "IS-A" to describe a relationship of services strikes me
as, at best, an unfortunate choice. Perhaps I need to go learn the
history of the term, but "ACTS-LIKE" seems like a much better choice,
"IS-A" being reserved to mean the more intuitive "satisfies the same
preconditions".

Now, if we are all lucky, somebody will be able to follow up to this with
a pointer to a convincing argument in favor the current interpretation of
"IS-A". The arguments I've seen thus far, including Owen's above, have
not been terribly convincing - the feel of a contrived post-mortem
justification has shared by them all.

Danil

Roger L. Cauvin

unread,
Nov 2, 2000, 11:07:15 PM11/2/00
to
"Danil" <da...@ultranet.com> wrote in message
news:MPG.146b82052...@enews.newsguy.com...

>
> Perhaps I'm naive, but the square/rectangle debate has long
> seemed pretty straight forward to me.
>
> Put simply, the conflict is simply one of "objects have
> properties" versus "objects provide services".

Objects have behavior. Behavior may be static; we sometimes call this sort
of behavior a "property". Behavior may be dynamic; we sometimes call this
sort of behavior "functionality" or a "service".

> The choice of "IS-A" to describe a relationship of services
> strikes me as, at best, an unfortunate choice. Perhaps I
> need to go learn the history of the term, but "ACTS-LIKE"
> seems like a much better choice, "IS-A" being reserved to
> mean the more intuitive "satisfies the same preconditions".

"IS-A" entails "ACTS-LIKE". Imagine, for a moment, that we're not talking
about software development. When
we say, "an A is a B" we mean "for every x, if x is an A, then x is a B".
Any property or behavior that we ascribe to Bs must then also apply to As,
or we can derive a logical contradiction. If every B acts a certain way,
but A doesn't act that way, then we wouldn't say that A is a B. Therefore,
strict substitutability of concepts in the "real world" corresponds exactly
to substitutability of software objects.

> Now, if we are all lucky, somebody will be able to follow up
> to this with a pointer to a convincing argument in favor the
> current interpretation of "IS-A".

I hope you're convinced by the argument above. If not, please provide us
with an example of a strict IS-A relationship, inside or outside of the
software domain, in which behavioral substitutability does not hold. In the
case of squares and rectangles that conform to the strict geometric
definitions (i.e. they don't promise mutability), behavioral
substitutability clearly does hold.

dmitr...@my-deja.com

unread,
Nov 3, 2000, 7:43:21 AM11/3/00
to
In article <8ttdi8$6eob$1...@ID-20080.news.dfncis.de>,

Just some footnotes:

Things in a program are not matematical objects. They only model them:

1
1.0
0.1e+10
"1"
one

In mathematics one cannot substitute 1 for 1.0, because they ARE same.
LSP is a tautology in mathematics.

The "real world" as-it-is, rather belongs to the realm of religion. The
"real world" as we sense it, is governed by fuzzy logic. Its sets are
fuzzy, so "x belongs to A" is of [0,1], not of {0,1}

In the "real world" a greyhound classifies a mechanical rabbit as an
object of the class rabbits (for it obviously has long ears) and applies
the method "catch" to it. Such negligence is usually punished by
digestion problems followed by an appropriate exception.

Roger L. Cauvin

unread,
Nov 3, 2000, 9:32:07 AM11/3/00
to
<dmitr...@my-deja.com> wrote in message
news:8tubt8$56v$1...@nnrp1.deja.com...
> In article <8ttdi8$6eob$1...@ID-20080.news.dfncis.de>,

>
> Things in a program are not matematical objects.

I don't remember anyone claiming that software objects are mathematical
objects, only that can represent them and any IS-A relationship that exists
between them.

> They only model them:
>
> 1
> 1.0
> 0.1e+10
> "1"
> one
>
> In mathematics one cannot substitute 1 for 1.0, because
> they ARE same.

I'm afraid I don't understand your point here.

> LSP is a tautology in mathematics.
>
> The "real world" as-it-is, rather belongs to the realm
> of religion. The "real world" as we sense it, is
> governed by fuzzy logic.

Our perceptions of the world are uncertain, and the language we use to
describe it is often imprecise. However, this fact only serves to confirm
the IS-A rule. For when we try to model imprecise concepts in software, we
often stipulate more precise concepts that clearly either have an IS-A
relationship or do not. Once we analyze and refine the concepts in this
manner, it becomes clear whether to model the relationship with inheritance.

> In the "real world" a greyhound classifies a mechanical
> rabbit as an object of the class rabbits (for it obviously
> has long ears) and applies the method "catch" to it.
> Such negligence is usually punished by digestion
> problems followed by an appropriate exception.

Upon which the greyhound realizes that the mechanical rabbit is not a animal
rabbit, because it does not exhibit the same behavior when eaten. It is
therefore not substitutable, confirming the IS-A guideline once again.

mike_in...@my-deja.com

unread,
Nov 3, 2000, 10:08:45 AM11/3/00
to

> A Square is a Rectangle with sides of equal length. That's it, really.
> I would not make a seperate Square class. Maybe you could even add
> an "isSquare" method to see if a Rectangle is indeed a Square
(although
> a client of the class could simply deduce that itself). Once you
change
> it's height or width though, the thing is simply not a Square anymore.
> Greetz,
>
> RS

I think this is exactly right -- being a square is a particular state
of a rectangle (ditto with a ellipse and circle) and using inheritance
doesn't add anything to the model.

Mike

Danil

unread,
Nov 3, 2000, 10:13:09 AM11/3/00
to
In article <8ttdi8$6eob$1...@ID-20080.news.dfncis.de>, rca...@homemail.com
says...

> Objects have behavior. Behavior may be static; we sometimes call this sort
> of behavior a "property".

No. I'm sorry, but no.[1]

<HumptyDumpty>

When I use a word, it means just what I choose it to mean --
neither more nor less.

</HumptyDumpty>

More specifically, the definition above requires at least one of the
words in it to take on a meaning which is not consistent with common
every day use.

And the same problem holds true for IS-A. The evidence, of course, is
the large number of people who trip over it.

IS-A may have been the best choice of a bad lot, but it has the feel of a
term adopted by people who already knew about behavioral
substitutability, without consideration for what it would mean to people
who didn't. I just don't believe that upper case letters are enough of a
wakeup call.

The consistency of IS-A's definition within the framework is not evidence
that "IS-A" is the appropriate spelling. To borrow from Feynmann, you
could have used WALAXIES-A just as well, and without conflicting with the
preconceptions of the audience.

Danil

[1] Width is not a verb.

dmitr...@my-deja.com

unread,
Nov 4, 2000, 3:00:00 AM11/4/00
to
In article <8tui5s$8lv2$1...@ID-20080.news.dfncis.de>,

"Roger L. Cauvin" <rca...@homemail.com> wrote:
> <dmitr...@my-deja.com> wrote in message
> news:8tubt8$56v$1...@nnrp1.deja.com...
> > In article <8ttdi8$6eob$1...@ID-20080.news.dfncis.de>,
> >
> > Things in a program are not matematical objects.
>
> I don't remember anyone claiming that software objects are
mathematical
> objects, only that can represent them and any IS-A relationship that
exists
> between them.

So, there are many ways one may represent same mathematical things
which includes IS-A relationship itself.

> > They only model them:

> > In mathematics one cannot substitute 1 for 1.0, because
> > they ARE same.
>
> I'm afraid I don't understand your point here.

1 and 1.0 denote the same thing. 1 cannot be created, moved,
substituted or destroyed. There is exactly only one 1.

> > LSP is a tautology in mathematics.
> >
> > The "real world" as-it-is, rather belongs to the realm
> > of religion. The "real world" as we sense it, is
> > governed by fuzzy logic.
>
> Our perceptions of the world are uncertain, and the language we use to
> describe it is often imprecise. However, this fact only serves to
confirm
> the IS-A rule. For when we try to model imprecise concepts in
software, we
> often stipulate more precise concepts that clearly either have an IS-A
> relationship or do not. Once we analyze and refine the concepts in
this
> manner, it becomes clear whether to model the relationship with
inheritance.

OK. Now we have a land under feet. Here is the model:

Mathematics Language
set ----------> type
subset -------> subtype
element ------> value

The problem here is that a subtype is not a subset of the type. It is
another type, because its values are not of the base type:

type Rectangle is record
Width : Float;
Height : Float;
end record;

type Square is record
Side : Float;
end record;

Here LSP does not work. But this does not mean that this model cannot
be made consistent. Actually it is widely used for built-in types
(compare Integer and Float) and usually is supported by the compiler.
Unfortunately it is not (fully) supported for user-defined types in the
languages I know.

Of course, there could be other models, like:

Mathematics Language
set ----------> type
subset -------> type (same)
element ------> value

Here LSP works, but some extra logic should be implemented to
distinguish elements of the set and the subset. It is how most people
are trying to solve Circle/Ellipse. I find it clumsy.

The point is that there could be many different objects that model the
same thing. Then we should distinguish IS-A from the thing that models
it. LSP is a model of IS-A. It is a bad model, because it is too
restrictive. A more generalized approach is:

Substantivity:
--------------
1. Direct (X is of type A)

2. Indirect (X is not of type A and has to be converted to A)

2.A. LSP. X contains a value of A. The conversion is a reference shift,
no new object is created.

2.B. non-LSP. The conversion creates a new temporal object.

> > In the "real world" a greyhound classifies a mechanical
> > rabbit as an object of the class rabbits (for it obviously
> > has long ears) and applies the method "catch" to it.
> > Such negligence is usually punished by digestion
> > problems followed by an appropriate exception.
>
> Upon which the greyhound realizes that the mechanical rabbit is not a
animal
> rabbit, because it does not exhibit the same behavior when eaten. It
is
> therefore not substitutable, confirming the IS-A guideline once again.

Yes, but greyhounds work!

dani...@my-deja.com

unread,
Nov 4, 2000, 3:00:00 AM11/4/00
to
Lorin <hawke...@my-deja.com> wrote:
> I've been reading Object-Oriented Software Construction by Meyer, and I
> reached the chapter where he talks about different kinds of inheritance
> (Ch 24). He mentions "restriction inheritenace" as a valid use of
> inheritance, giving an example of a Square class inheriting from a
> Rectangle class.
>
> This really surprised me.

>
> So, who's right? Is it a language thing (i.e. you can't get away with
> this in C++, but in Eiffel there are mechanisms to avoid this problem
> like assertions)? Is this an unresolved issue?

Eiffel doesn't have private inheritenace. Instead, they use public
inheritenace and use invariant checks to insure that the objects involved
don't become invalid. As Mr. Meyer says in that chapter, the world is not a
perfect place, sometimes we have to use object A as if it were a type of
Class B even though it doesn't quite fit...

Peter Luschny

unread,
Nov 4, 2000, 3:00:00 AM11/4/00
to
Is a One a One?

<dmitr...@my-deja.com> schrieb im Newsbeitrag
news:8u0n3v$2i6$1...@nnrp1.deja.com...
> In article <8tui5s$8lv2$1...@ID-20080.news.dfncis.de>,


> "Roger L. Cauvin" <rca...@homemail.com> wrote:

> > <dmitr...@my-deja.com> wrote in message
> > news:8tubt8$56v$1...@nnrp1.deja.com...
> > > In article <8ttdi8$6eob$1...@ID-20080.news.dfncis.de>,

> > > In mathematics one cannot substitute 1 for 1.0, because
> > > they ARE same.

> > I'm afraid I don't understand your point here.

> 1 and 1.0 denote the same thing. 1 cannot be created, moved,


> substituted or destroyed. There is exactly only one 1.

No. There is the natural number 1. There is the integer 1. There is
the real number 1.0. And so on ...
Definitively, the natural number 1 is not the same thing as the real number
1.0.

Regards,
Peter


Roger L. Cauvin

unread,
Nov 4, 2000, 3:00:00 AM11/4/00
to
"Danil" <da...@ultranet.com> wrote in message
news:MPG.146ca015f...@enews.newsguy.com...

> In article <8ttdi8$6eob$1...@ID-20080.news.dfncis.de>, rca...@homemail.com
> says...
>> Objects have behavior. Behavior may be static; we
>> sometimes call this sort of behavior a "property".
>
> No. I'm sorry, but no.[1]
> [1] Width is not a verb.

My premise isn't that "width" itself is a verb; only that "object has a
width property" implies "object exhibits width", and vice versa. In the
real world, properties denote static behavior.

In the realm of software, we frequently provide accessors for properties.
We also say that these accessors define the object's behavior, for we may
modify their behavior by overriding them.

> More specifically, the definition above requires at least
> one of the words in it to take on a meaning which is
> not consistent with common every day use.

I disagree with your assessment of common use. Take any property of a
concept. You can translate it into a sentence in which the concept exhibits
the property. The word "exhibits" is a verb.

> And the same problem holds true for IS-A. The evidence,
> of course, is the large number of people who trip over it.

> The consistency of IS-A's definition within the framework


> is not evidence that "IS-A" is the appropriate spelling. To
> borrow from Feynmann, you could have used WALAXIES-A
> just as well, and without conflicting with the preconceptions
> of the audience.

The IS-A framework is not merely internally consistent - it corresponds well
to how we use the terms "object", "property", "is", and "behavior" in the
both the real world and OO software domain. The fact that people trip up on
it does not belie this claim about usage. It only shows that many people
use the words in a particular manner without thinking about it.

I think it's unfair to characterize this line of reasoning as ad hoc. As a
philosophy major and OO software developer, I recognized the correspondence
between IS-A, behavioral substitutability, and inheritance long before I
ever considered the circle/ellipse and square/rectangle dilemmas.

Dan Cohen

unread,
Nov 4, 2000, 3:00:00 AM11/4/00
to

dmitr...@my-deja.com wrote:
>
> OK. Now we have a land under feet. Here is the model:
>
> Mathematics Language
> set ----------> type
> subset -------> subtype
> element ------> value
>
> The problem here is that a subtype is not a subset of the type. It is
> another type, because its values are not of the base type:
>

>...


> > > In the "real world" a greyhound classifies a mechanical
> > > rabbit as an object of the class rabbits (for it obviously
> > > has long ears) and applies the method "catch" to it.
> > > Such negligence is usually punished by digestion
> > > problems followed by an appropriate exception.
> >
> > Upon which the greyhound realizes that the mechanical rabbit is not a
> animal
> > rabbit, because it does not exhibit the same behavior when eaten. It
> is
> > therefore not substitutable, confirming the IS-A guideline once again.
>

> Yes, but greyhounds work!

Dmitry, your posting is a delight! I wish more programmers could read
your comparison of paradigms. But (you knew I'd supply a 'but'!) when
you switched from paradigms to pooches, you de-railed.

No self-respecting and intelligent greyhound would ever mistake a
'model' of a rabbit for the real thing. The dogs don't chase the rabbits
at all! All good racers know, it's easier, more efficient, and faster to
follow a pace-rabbit, than to set the pace yourself! I don't know if any
greyhound has ever caught a chase rabbit, but if he/she did, you can bet
he'd have fun tearing it apart. But eat it? No, that's reserved for
'objects' that actually taste good.

As for their enthusiasm: that's real. Ask any athlete if he likes his
sport.

So, I can repeat: Never mistake the model object for the real object.

-- Dan Cohen in Calgary

Robert C. Martin

unread,
Nov 4, 2000, 3:00:00 AM11/4/00
to

"Graham Perkins" <g...@dmu.ac.uk> wrote in message
news:3A0051F9...@dmu.ac.uk...

In Smalltalk, a user of a Rectangle would expect that setting the height
would not change the width. Passing a square object that responded to
setHeight by also changing the width would confuse the user. The LSP holds
irrespective of whether the language is statically typed or dynamically
typed.


--

Robert C. Martin | "Uncle Bob" | Software Consultants
Object Mentor Inc. | rma...@objectmentor.com | We'll help you get
PO Box 5757 | Tel: (800) 338-6716 | your projects done.
565 Lakeview Pkwy | Fax: (847) 573-1658 | www.objectmentor.com
Suite 135 | | www.XProgramming.com
Vernon Hills, IL, | Training and Mentoring | www.junit.org
60061 | OO, XP, Java, C++, Python|

"One of the great commandments of science is:
'Mistrust arguments from authority.'" -- Carl Sagan


Robert C. Martin

unread,
Nov 4, 2000, 3:00:00 AM11/4/00
to

<dmitr...@my-deja.com> wrote in message
news:8trjga$r70$1...@nnrp1.deja.com...
> In article <3a00abd6...@news.waitrose.com>,

> Absolutely. And what LSP does? It tries to enforce any square variable
> be able to represent any rectangle, which is wrong.

I think you have things twisted around. The issue is whether inheritance is
an appropriate relationship between Square and Rectangle. It is clear,
regardless of the Square/Rectangle issue that LSP is a valid set of
constraints upon inheritance. Any true subtype must be substitutable for
its supertype.

Now, is a square substitutable for a rectangle in all its usage contexts?
Clearly it is, if we are talking about geometry. However when we talk about
software, and the behaviors one would expect of squares and rectangles in a
software problem, we usually find that the behavior of square is not
substitutable for the behavior of rectangle. This means that the LSP is
violated and that the software square is not a true subtype of the software
rectangle. This means that inheritance is likely not the relationship that
should be used between these two pieces of software.

al...@my-deja.com

unread,
Nov 5, 2000, 3:00:00 AM11/5/00
to
In article <8trjga$r70$1...@nnrp1.deja.com>,
dmitr...@my-deja.com wrote:
[snip]

> I would formulate it as follows:
>
> 1. Mathematically "square is a rectangle" means that any square
> belongs to the set of all rectangles.

Yep.

> 2. In a programming language, "square is a rectangle" means that the
> type square is a subtype of the type rectangle.

Ditto.

> Now, the question is, can 1. be modelled using 2.?

Sure, if we take another leaf from the book of mathematics...:-)


> The answer is there are cases when it is impossible if LSP is used.

Only if you accept the existence of "mutable objects" -- THAT
is where math<->programming correspondence break down.

Consider a language which is single-assignment (like Erlang,
or, I believe, Clean), or otherwise focuses on 'immutable objects'
(like Haskell -- basically, immutable objects are the staple
of functional programming, though some FP languages to allow
some 'mutation' under explicit request). If an object, once
created, does not change, then IS-A/LSP regains its full
status. Only mutating-operations undermine the correspondence
between LSP and the common mathematical sense of IS-A!

An example I think highly relevant is in parameterized
containers. Is a bag-of-bananas a bag-of-fruit (assuming
a banana IS-A fruit, of course)? Not if mutation exists:
if a method expects a mutable argument bag-of-fruit, you
can't pass it an object which knows it's a bag-of-bananas,
else the method might insert an apple in the bag and break
the bag-of-bananas' semantics! THIS result is one of the
least-intuitive, hardest-to-teach issues in OO analysis --
it leads directly to Eiffel's troubles with covariance and
Java's runtime-type-error under such conditions, so it's
not a highly abstract/theoretical issue, either.

But no-mutation is the only way I know to reconcile OO
(and LSP) with maths & common sense in this regard. Still,
mutating-objects ARE handy, so I'm not surprised most
languages sacrifice purity and simplicity on the altar
of power and efficiency. But from mutation come these
contradictions, and there ARE languages which can do
better (though the degree of OO'ness in Haskell itself,
say, is scarce, there _are_ variants such as O'Haskell
which join OO and FP paradigms -- O'Caml too, of course,
probably the best-performing FP language today [got 1st
AND 2nd prize in the latest FP championship...], but that
is not very 'pure' mutationwise:-).


Alex

bran...@cix.compulink.co.uk

unread,
Nov 5, 2000, 3:00:00 AM11/5/00
to
dani...@my-deja.com () wrote (abridged):

> Eiffel doesn't have private inheritenace.

Eiffel has selective export, which you can use to "hide" a feature. The
result is similar to private inheritance; it means that the subclass no
longer conforms to the base class from the point of view of anyone using
the hidden feature. It does conform if you don't use the feature. This
goes against Liskov but is arguably what you want. A square is a
rectangle as long as you don't want to resize it.

I don't know the current state of art on implementing this. Meyer wrote
articles saying that it was checkable statically, but (a) it involved a
non-local analysis and (b) for a long time his compiler didn't implement
it right and you could provoke core dumps.

(C++ also lets you hide features, but according to its type system the
subclass still conforms to the base class even if you do use the
supposedly non-existent feature. However, you can implement the feature
to perform a run-time check.)

Dave Harris, Nottingham, UK | "Weave a circle round him thrice,
bran...@cix.co.uk | And close your eyes with holy dread,
| For he on honey dew hath fed
http://www.bhresearch.co.uk/ | And drunk the milk of Paradise."

Robert C. Martin

unread,
Nov 5, 2000, 3:00:00 AM11/5/00
to

<al...@my-deja.com> wrote in message news:8u3f11$1cl$1...@nnrp1.deja.com...

> If an object, once
> created, does not change, then IS-A/LSP regains its full
> status.

The LSP does not have a status to change. The LSP simply defines what it
means to be a subtype.

> Only mutating-operations undermine the correspondence
> between LSP and the common mathematical sense of IS-A!

Mutability has nothing to do with it. The LSP says that s is a subtype of t
if s is substitutable for t in all contexts. Whether s and t are mutable or
immutable is irrelevant.

It turns out that it is some of the mutator methods of Square and Rectangle
that result in substitution failures that prove that Square is not a subtype
of Rectangle. If you take those mutator methods away, then Square is
substitutable for Rectangle and the subtype relationship *might* hold.
(Though having Square inherit both a height and a width is still wierd, and
wasteful of space).

The term 'ISA' can mean many things. It does not apply accross disciplines.
An ISA between two entities in geometry does not necessarily mean that the
ISA relationship holds between analogous entities in software.

> But no-mutation is the only way I know to reconcile OO
> (and LSP) with maths & common sense in this regard.

There is nothing to reconcile. Software and math are separate disciplines.
The fact that a square is no less constrained (mathematical ISA) than a
rectangle has no bearing upon whether a software object that represents a
square should inherit all the variables and functions of the software object
that represents a rectangle.

dmitr...@my-deja.com

unread,
Nov 6, 2000, 3:00:00 AM11/6/00
to
In article <8u18ut$lj4$1...@ac1.ewetel.de>,

"Peter Luschny" <pe...@luschny.de> wrote:
> Is a One a One?
>
> <dmitr...@my-deja.com> schrieb im Newsbeitrag
> news:8u0n3v$2i6$1...@nnrp1.deja.com...
> > In article <8tui5s$8lv2$1...@ID-20080.news.dfncis.de>,

> > "Roger L. Cauvin" <rca...@homemail.com> wrote:
> > > <dmitr...@my-deja.com> wrote in message
> > > news:8tubt8$56v$1...@nnrp1.deja.com...
> > > > In article <8ttdi8$6eob$1...@ID-20080.news.dfncis.de>,
>
> > > > In mathematics one cannot substitute 1 for 1.0, because
> > > > they ARE same.
>
> > > I'm afraid I don't understand your point here.
>
> > 1 and 1.0 denote the same thing. 1 cannot be created, moved,
> > substituted or destroyed. There is exactly only one 1.
>
> No. There is the natural number 1. There is the integer 1. There is
> the real number 1.0. And so on ...
> Definitively, the natural number 1 is not the same thing as the real
number
> 1.0.

N is a subset of R

q.e.d.

dmitr...@my-deja.com

unread,
Nov 6, 2000, 3:00:00 AM11/6/00
to
In article <8u3f11$1cl$1...@nnrp1.deja.com>,

al...@my-deja.com wrote:
> In article <8trjga$r70$1...@nnrp1.deja.com>,
> dmitr...@my-deja.com wrote:
> [snip]
> > I would formulate it as follows:
> >
> > 1. Mathematically "square is a rectangle" means that any square
> > belongs to the set of all rectangles.
>
> Yep.

>
> > 2. In a programming language, "square is a rectangle" means that the
> > type square is a subtype of the type rectangle.
>
> Ditto.

>
> > Now, the question is, can 1. be modelled using 2.?
>
> Sure, if we take another leaf from the book of mathematics...:-)
>
> > The answer is there are cases when it is impossible if LSP is used.
>
> Only if you accept the existence of "mutable objects" -- THAT
> is where math<->programming correspondence break down.

I think that it is more or less a problem of terminology. There are
neither numbers nor objects in a programming language. We model objects
using values and variables. Object is an abstraction. We should clearly
understand where it works. Mathematical square is not an object, for it
has no state (it IS a state). A method "resize" cannot modify a
rectangle, it produces (or to be more puristic refers to) a NEW one. It
will never work this way! However, what we actually want, is a thing can
refer mathematical squares or rectangles. Of course, we call it also
square/rectangle and the mess begins.

> Consider a language which is single-assignment (like Erlang,
> or, I believe, Clean), or otherwise focuses on 'immutable objects'
> (like Haskell -- basically, immutable objects are the staple
> of functional programming, though some FP languages to allow

> some 'mutation' under explicit request). If an object, once


> created, does not change, then IS-A/LSP regains its full

> status. Only mutating-operations undermine the correspondence


> between LSP and the common mathematical sense of IS-A!

Yep. There is no assignment in mathematics. As simple as that. In
mathematics LSP is a tautology, 1 is always 1. Only in programming
languages, where "all ones are same, but there are ones that more ones
than others", LSP shows its malicious face.

But my point was that it is not a big problem. For instance, in Ada you
can write:

procedure GetFromKeyboard (X : in out Float);
-- reads a floating-point from the keyboard input
.. and then

I : Integer;

GetFromKeyborad (Float (I));

It works. No matter that the formal parameter is IN-OUT!

Now imagine, that Integer is a subtype of Float, which is wrong in Ada,
but correct with some limitations in C++. Then you would be able to
write:

GetFromKeyborad (I);

> An example I think highly relevant is in parameterized
> containers. Is a bag-of-bananas a bag-of-fruit (assuming
> a banana IS-A fruit, of course)? Not if mutation exists:
> if a method expects a mutable argument bag-of-fruit, you
> can't pass it an object which knows it's a bag-of-bananas,
> else the method might insert an apple in the bag and break
> the bag-of-bananas' semantics! THIS result is one of the
> least-intuitive, hardest-to-teach issues in OO analysis --
> it leads directly to Eiffel's troubles with covariance and
> Java's runtime-type-error under such conditions, so it's
> not a highly abstract/theoretical issue, either.

Yes.

The question is, do you want to pass an actual of bag-of-bananas to a
formal of bag-of-fruit in IN-OUT mode? If you want it, you should be
prepared that in some cases an attempt to put an apple into a
bag-of-bananas, will be discovered only at run-time. That's the price.

[ In this forum where the majority votes for dynamic typed languages
that should be not an issue, right? (:-)) ]

> But no-mutation is the only way I know to reconcile OO
> (and LSP) with maths & common sense in this regard.

As for common sense, I think it is OO, that is derived from common
sense, not mathematics (:-)). It is where strengths and weaknesses of OO
come from. And, IMO, nobody will be able to mate objects with pure
mathematics. Neither it is necessary.

> Still, mutating-objects ARE handy,

... and HOW! (:-))

> so I'm not surprised most
> languages sacrifice purity and simplicity on the altar
> of power and efficiency. But from mutation come these
> contradictions, and there ARE languages which can do
> better (though the degree of OO'ness in Haskell itself,
> say, is scarce, there _are_ variants such as O'Haskell
> which join OO and FP paradigms -- O'Caml too, of course,
> probably the best-performing FP language today [got 1st
> AND 2nd prize in the latest FP championship...], but that
> is not very 'pure' mutationwise:-).

--

dmitr...@my-deja.com

unread,
Nov 6, 2000, 3:00:00 AM11/6/00
to
In article <t094p2d...@news.supernews.com>,

"Robert C. Martin" <rma...@objectmentor.com> wrote:
>
> <dmitr...@my-deja.com> wrote in message
> news:8trjga$r70$1...@nnrp1.deja.com...
> > In article <3a00abd6...@news.waitrose.com>,
>
> > Absolutely. And what LSP does? It tries to enforce any square
> > variable be able to represent any rectangle, which is wrong.
>
> I think you have things twisted around. The issue is whether
> inheritance is an appropriate relationship between Square and
> Rectangle.

Yes it is.

> It is clear,
> regardless of the Square/Rectangle issue that LSP is a valid set of
> constraints upon inheritance.

It depends on how you define LSP. In its naive form the answer is no.

> Any true subtype must be substitutable for
> its supertype.

Yes. The question is how. Naive LSP gives an incomplete answer (IMO).

> Now, is a square substitutable for a rectangle in all its usage
> contexts? Clearly it is, if we are talking about geometry.
> However when we talk about software, and the behaviors one would
> expect of squares and rectangles in a software problem, we usually
> find that the behavior of square is not substitutable for the behavior
> of rectangle. This means that the LSP is
> violated and that the software square is not a true subtype of the
> software rectangle. This means that inheritance is likely not the
> relationship that should be used between these two pieces of
> software.

[ strange to hear such things from an OO proponent (:-)) ]

Again yes, if inheritance is conform to LSP.

What I am trying to say is that inheritance is richer than simply
"subtyping by extension". So the software square could be made a subtype
of the software rectangle. Where "be a subtype" means presense of
conversions between them. LSP also requires this, but it also says that
conversions were merely reference shifts. I propose to relax LSP to
allow all conversions.

Anatoli Tubman

unread,
Nov 6, 2000, 3:00:00 AM11/6/00
to
In article <8u5uk2$s1a$1...@nnrp1.deja.com>,

dmitr...@my-deja.com wrote:
> N is a subset of R

Sometimes it is convenient to think that N is a subset of R,
and sometimes it is convenient to think that N is isomorphic
to N' which is a subset of R. In programming the second p.o.v.
is usually more productive.
--
Regards
Anatoli (anatoli<at>ptc<dot>com) opinions aren't

Anatoli Tubman

unread,
Nov 6, 2000, 3:00:00 AM11/6/00
to
In article <8u0n3v$2i6$1...@nnrp1.deja.com>,

dmitr...@my-deja.com wrote:
> OK. Now we have a land under feet. Here is the model:
>
> Mathematics Language
> set ----------> type
> subset -------> subtype
> element ------> value
>
> The problem here is that a subtype is not a subset of the type. It is
> another type, because its values are not of the base type:
>
> type Rectangle is record
> Width : Float;
> Height : Float;
> end record;
>
> type Square is record
> Side : Float;
> end record;

1) Square as you wrote it isn't a subtype (and not a subset)
of Rectangle as you wrote it, so this proves nothing.

2) For any type A and its subtype B, a value (or, in most languages,
reference or pointer) of type B is *also* of type A (by definition).
Some languages say that B values are not A values but are convertible
to type A. But we can (as is done usually in math) identify B with
its image in A under this conversion. So the set of all B values is a
very nice subset of the set of all A values.

--
Regards
Anatoli (anatoli<at>ptc<dot>com) opinions aren't

Robert C. Martin

unread,
Nov 6, 2000, 3:00:00 AM11/6/00
to

<dmitr...@my-deja.com> wrote in message
news:8u68b5$2kf$1...@nnrp1.deja.com...

>
> What I am trying to say is that inheritance is richer than simply
> "subtyping by extension".

Inheritance is nothing more than one data structure basing itself upon
another. The functions and variables of the base exist also in the
derivative. That's all. We sometimes use this to create subtype
relationships where derivatives can substitute for the base in certain
contexts.

> So the software square could be made a subtype
> of the software rectangle. Where "be a subtype" means presense of
> conversions between them.

Yes, you could do this. SetWidth could *return* a rectangle rather than
change the existing rectangle. And if you do this, then the LSP is not
violated. The question is, why is it important for Square to be a subtype
of Rectangle? What benefit does the programmer gain from this?

Owen Rees

unread,
Nov 6, 2000, 8:16:11 PM11/6/00
to
On Mon, 06 Nov 2000 12:31:32 GMT, dmitr...@my-deja.com wrote:

>What I am trying to say is that inheritance is richer than simply

>"subtyping by extension". So the software square could be made a subtype


>of the software rectangle. Where "be a subtype" means presense of

>conversions between them. LSP also requires this, but it also says that
>conversions were merely reference shifts. I propose to relax LSP to
>allow all conversions.

Perhaps you are looking for the kind of inheritance that is
appropriate to AI knowledge representation. If so, I suggest you read
"The Mathematics of Inheritance Systems" by Touretzky. Chapter 2 "A
Theory of IS-A Inheritance" presents a generic inheritance system with
exceptions.

I would advise against using that kind of inheritance to construct
relationships between program components; it does not generate a
subtype relation that is useful in type checking of programs.


--
Owen Rees - its just me writing this, see
<http://www.users.waitrose.com/~owenrees/index.html#disclaimer>

dmitr...@my-deja.com

unread,
Nov 7, 2000, 3:00:00 AM11/7/00
to
In article <8u6hfq$a7d$1...@nnrp1.deja.com>,

Anatoli Tubman <ana...@my-deja.com> wrote:
> In article <8u0n3v$2i6$1...@nnrp1.deja.com>,
> dmitr...@my-deja.com wrote:
> > OK. Now we have a land under feet. Here is the model:
> >
> > Mathematics Language
> > set ----------> type
> > subset -------> subtype
> > element ------> value
> >
> > The problem here is that a subtype is not a subset of the type. It
is
> > another type, because its values are not of the base type:
> >
> > type Rectangle is record
> > Width : Float;
> > Height : Float;
> > end record;
> >
> > type Square is record
> > Side : Float;
> > end record;
>
> 1) Square as you wrote it isn't a subtype (and not a subset)
> of Rectangle as you wrote it, so this proves nothing.

Which depends on how you define the term "subtype".

> 2) For any type A and its subtype B, a value (or, in most languages,
> reference or pointer) of type B is *also* of type A (by
> definition).

This definition is an equivalent to LSP. It follows from LSP and LSP can
be derived from it.

> Some languages say that B values are not A values but are
> convertible
> to type A. But we can (as is done usually in math) identify B with
> its image in A under this conversion. So the set of all B values is
> a very nice subset of the set of all A values.

Yes, this is what I want to have: to treat IS-A as an isomorphism.

dmitr...@my-deja.com

unread,
Nov 7, 2000, 3:00:00 AM11/7/00
to
In article <t0emiqq...@news.supernews.com>,

"Robert C. Martin" <rma...@objectmentor.com> wrote:
>
> <dmitr...@my-deja.com> wrote in message
> news:8u68b5$2kf$1...@nnrp1.deja.com...
> >
> > What I am trying to say is that inheritance is richer than simply
> > "subtyping by extension".
>
> Inheritance is nothing more than one data structure basing itself upon
> another. The functions and variables of the base exist also in the
> derivative. That's all. We sometimes use this to create subtype
> relationships where derivatives can substitute for the base in certain
> contexts.

I do not like this definition, because it exposes implementation
details.

> > So the software square could be made a subtype
> > of the software rectangle. Where "be a subtype" means presense of
> > conversions between them.
>

> Yes, you could do this. SetWidth could *return* a rectangle rather
> than change the existing rectangle. And if you do this, then the LSP
> is not violated. The question is, why is it important for Square to
> be a subtype of Rectangle? What benefit does the programmer gain from
> this?

1. Code reuse. Methods of Rectangle are inherited by Square. Note,
including Resize.

2. An ability to have lists of rectangles containing squares.

3. Generally speaking, to have it OO (:-))

dmitr...@my-deja.com

unread,
Nov 7, 2000, 3:00:00 AM11/7/00
to
In article <3a074d61...@news.waitrose.com>,

owen...@waitrose.deletethis.com (Owen Rees) wrote:
> On Mon, 06 Nov 2000 12:31:32 GMT, dmitr...@my-deja.com wrote:
>
> >What I am trying to say is that inheritance is richer than simply
> >"subtyping by extension". So the software square could be made a
subtype
> >of the software rectangle. Where "be a subtype" means presense of
> >conversions between them. LSP also requires this, but it also says
that
> >conversions were merely reference shifts. I propose to relax LSP to
> >allow all conversions.
>
> Perhaps you are looking for the kind of inheritance that is
> appropriate to AI knowledge representation. If so, I suggest you read
> "The Mathematics of Inheritance Systems" by Touretzky. Chapter 2 "A
> Theory of IS-A Inheritance" presents a generic inheritance system with
> exceptions.
>
> I would advise against using that kind of inheritance to construct
> relationships between program components; it does not generate a
> subtype relation that is useful in type checking of programs.

Surely it does. When you write Put(Float(123)); it is pretty type safe.
It remains safe if 123 is converted to Float implicitly: Put(123);

Graham Perkins

unread,
Nov 7, 2000, 3:00:00 AM11/7/00
to
> > If you have dynamic type system, there is no LSP to misuse.
..

> In Smalltalk, a user of a Rectangle would expect that setting the height
> would not change the width. Passing a square object that responded to
> setHeight by also changing the width would confuse the user. The LSP holds
> irrespective of whether the language is statically typed or dynamically
> typed.

Well, if you're allowed to talk about the intended type of a variable
rather than its declared type, then I'll grant that LSP is relevant to
dynamic typed languages. But I do say *TYPE*, not class. And it's
quite
clear that the Smalltalk class hierarchy is not a type hierarchy! Maybe
much of it is, but there are very many places where a subclass is not a
subtype, also many cases of subtype not a subclass.

Robert C. Martin

unread,
Nov 7, 2000, 3:00:00 AM11/7/00
to

<dmitr...@my-deja.com> wrote in message news:8u8f76$19$1...@nnrp1.deja.com...

> In article <t0emiqq...@news.supernews.com>,
> "Robert C. Martin" <rma...@objectmentor.com> wrote:
> >
> > <dmitr...@my-deja.com> wrote in message
> > news:8u68b5$2kf$1...@nnrp1.deja.com...
> > >
> > > What I am trying to say is that inheritance is richer than simply
> > > "subtyping by extension".
> >
> > Inheritance is nothing more than one data structure basing itself upon
> > another. The functions and variables of the base exist also in the
> > derivative. That's all. We sometimes use this to create subtype
> > relationships where derivatives can substitute for the base in certain
> > contexts.
>
> I do not like this definition, because it exposes implementation
> details.

Nevertheless, it is the definition you are faced with. You can't ignore it,
since it is an undeniable part of semantics of the programming language you
are using.

> > > So the software square could be made a subtype
> > > of the software rectangle. Where "be a subtype" means presense of
> > > conversions between them.
> >

> > Yes, you could do this. SetWidth could *return* a rectangle rather
> > than change the existing rectangle. And if you do this, then the LSP
> > is not violated. The question is, why is it important for Square to
> > be a subtype of Rectangle? What benefit does the programmer gain from
> > this?
>
> 1. Code reuse. Methods of Rectangle are inherited by Square. Note,
> including Resize.

Is that benefit worth the costs? One cost is the lack of mutators in
Rectangle. This means that every time you want to change the width or
height of the rectangle you must create a new one -- an expensive operation
at best.

Another cost is the fact that Square will have two variables within it, when
it only requires one. Square will take up twice the space it ought to.


A third cost is the high coupling of Square to Rectangle. Every time
Rectangle is changed for any reason, Square and its users will likely need
recompiling and redeploying.

Frankly, I don't see the benefit outweighing these costs.

> 2. An ability to have lists of rectangles containing squares.

This is a real benefit. There are cases in which I would pay all of the
above costs in order to avoid 'if' or 'switch' statements in dozens or
hundreds of clients. However, I'd need to know that those clients existed,
and really would need the 'if' or 'switch' statements.

> 3. Generally speaking, to have it OO (:-))

OO is not equivalent to "Square ISA rectangle". OO is a technology for
managing interdependencies between modules and structuring source code to be
less coupled. It has nothing to do with attempts to make programming
languages look like geometry.

dmitr...@my-deja.com

unread,
Nov 8, 2000, 3:00:00 AM11/8/00
to
In article <t0gaiqm...@news.supernews.com>,

"Robert C. Martin" <rma...@objectmentor.com> wrote:
>
> <dmitr...@my-deja.com> wrote in message
news:8u8f76$19$1...@nnrp1.deja.com...
> > In article <t0emiqq...@news.supernews.com>,
> > "Robert C. Martin" <rma...@objectmentor.com> wrote:
> > >
> > > <dmitr...@my-deja.com> wrote in message
> > > news:8u68b5$2kf$1...@nnrp1.deja.com...
> > > >
> > > > What I am trying to say is that inheritance is richer than simply
> > > > "subtyping by extension".
> > >
> > > Inheritance is nothing more than one data structure basing itself upon
> > > another. The functions and variables of the base exist also in the
> > > derivative. That's all. We sometimes use this to create subtype
> > > relationships where derivatives can substitute for the base in certain
> > > contexts.
> >
> > I do not like this definition, because it exposes implementation
> > details.
>
> Nevertheless, it is the definition you are faced with. You can't
> ignore it, since it is an undeniable part of semantics of the
> programming language you are using.

Or I switch to another language (:-))

> > > > So the software square could be made a subtype
> > > > of the software rectangle. Where "be a subtype" means presense

> > > > conversions between them.
> > >
> > > Yes, you could do this. SetWidth could *return* a rectangle
> > > rather than change the existing rectangle. And if you do this,
> > > then the LSP is not violated. The question is, why is it
> > > important for Square to be a subtype of Rectangle? What benefit
> > > does the programmer gain from this?
> >
> > 1. Code reuse. Methods of Rectangle are inherited by Square. Note,
> > including Resize.
>
> Is that benefit worth the costs? One cost is the lack of mutators in
> Rectangle. This means that every time you want to change the width or
> height of the rectangle you must create a new one -- an expensive
> operation at best.

Maybe, but if it is accepted for Integer<->Float, why should it be
forbidden for user-defined types?

Note also that advanced compilers (for instance gnat Ada) pass
relatively large record types rather by copy than by reference. On a
RISC processor it is more efficient than by-reference passing (which LSP
tries to enforce in all cases).

> Another cost is the fact that Square will have two variables within
> it, when it only requires one. Square will take up twice the space it
> ought to.

Why so? I would declare:

type Rectangle is record
Width : Float;
Height : Float;
end record;

type Square is record
Size : Float;
end record;

Where is extra space? It is LSP (subtyping by extension) that requires
extra space.

> A third cost is the high coupling of Square to Rectangle. Every time
> Rectangle is changed for any reason, Square and its users will likely
> need recompiling and redeploying.

1. They are coupled by geometric semantics.

2. More recompiling and redeploying would be necessary only if you have
C++ with its comic header files, or if you are trying to inline all
possible functions. And the situation will not change if you develop
Square and Rectangle independently.

3. non-LSP subtyping would also make possible multiple representations.
Consider:

type Complex_Canonical is ...;
type Complex_Polar is ...;

Let's make Complex_Polar subtype and supertype of Complex_Canonical.
After that one can mix instances of both types in all expressions. This
an example, where LSP fundamentally fails. Because unlike
Square/Rectange, Complex_Canonical/Polar are "same" without any
exceptions and still violate LSP.

> Frankly, I don't see the benefit outweighing these costs.

A real cost is that non-LSP subtyping require copy-in/copy-out parameter
passing. This might be unacceptable in many cases (for obvious reasons).
So LSP still has its place, but no more than as an implementation
detail, not as "the flagship of OO technology".

> > 2. An ability to have lists of rectangles containing squares.
>
> This is a real benefit. There are cases in which I would pay all of
> the above costs in order to avoid 'if' or 'switch' statements in
> dozens or hundreds of clients. However, I'd need to know that those
> clients existed, and really would need the 'if' or 'switch'
> statements.
>
> > 3. Generally speaking, to have it OO (:-))
>
> OO is not equivalent to "Square ISA rectangle". OO is a technology
> for managing interdependencies between modules and structuring source
> code to be less coupled. It has nothing to do with attempts to make
> programming languages look like geometry.

This is a view on programming in large. One could have OO in assembler.

But if we are speaking about programming languages then inheritance is
the native (only) mechanism of subtyping. If we drop subtyping what
would remain from OO support in the language?

sk...@my-deja.com

unread,
Nov 8, 2000, 3:00:00 AM11/8/00
to
In article <8u8f76$19$1...@nnrp1.deja.com>,
dmitr...@my-deja.com wrote:

>In article <t0emiqq...@news.supernews.com>,
> "Robert C. Martin" <rma...@objectmentor.com> wrote:

> > > What I am trying to say is that inheritance is richer than simply
> > > "subtyping by extension".

>> Inheritance is nothing more than one data structure basing itself
upon
>> another. The functions and variables of the base exist also in the
>> derivative. That's all. We sometimes use this to create subtype
>> relationships where derivatives can substitute for the base in
certain
>> contexts.

> I do not like this definition, because it exposes implementation
details.

Well it is a programmers definition, so one would expect to see the
"phrases" of programming in use, no ??

You are correct in stating that inheritance is "richer than simply
subtyping by extension" . The key concepts that inheritance convey are
generalisation and specialisation, analogy and deviation.

For the former, there is overlap between it and concepts such as
subtyping
and the LSP. For the latter, subtyping fails miserably (and often is the
cause of analysts/designers with incomplete understanding of OO trying
to
use the LSP as a "when all you have is a hammer ..." solution) .

>>The question is, why is it important for Square to be a subtype of
>>Rectangle? What benefit does the programmer gain from this?

>1. Code reuse. Methods of Rectangle are inherited by Square. Note,
>including Resize.

>2. An ability to have lists of rectangles containing squares.


>3. Generally speaking, to have it OO (:-))

What you are really looking for is for squares to acquire particular
properties of rectangles, and where those properties are of interest,
allow
rectangles and squares to be used interchangeably.

But as you have identified, mutability causes problems.

Analysis of the problem in fact shows that nearly all of the *mutable*
properties of rectangle are disjoint from the mutable properties of
square. Therefore squares and rectangles are only interchangeable when
the immutable (invariant) properties of rectangles are being accessed
[worse still, analysis shows how something as innocuous as choice of
property names can actually cause poor design decisions to be made
:-( ] .


Regards,
Steven Perryman

sk...@my-deja.com

unread,
Nov 8, 2000, 3:00:00 AM11/8/00
to
In article <t0gaiqm...@news.supernews.com>,

"Robert C. Martin" <rma...@objectmentor.com> wrote:

> OO is not equivalent to "Square ISA rectangle". OO is a technology
> for managing interdependencies between modules and structuring source
> code to be less coupled.

That is the definition of OOP, and parts of OOD.
It would be a poor, narrow-minded definition of OO as used in s/w
engineering throughout the lifecycle.

>It has nothing to do with attempts to make programming languages look
>like geometry.

The poster from what I see is not attempting to "make programming
languages look like geometry" . He is discussing what it is in OOP that
prevents him capturing what is able to be specified fairly easily on
paper in the geometry problem domain, and still reap the benefits that
OOP offers (s/w reuse etc) .

Anatoli Tubman

unread,
Nov 8, 2000, 3:00:00 AM11/8/00
to
In article <8u8f36$vus$1...@nnrp1.deja.com>,

dmitr...@my-deja.com wrote:
> > 1) Square as you wrote it isn't a subtype (and not a subset)
> > of Rectangle as you wrote it, so this proves nothing.
>
> Which depends on how you define the term "subtype".

I know of two definitions. One is language-pecific: if A is
substitutable for B according to the language rules, then A is
a subtype of B. The other one is LSP. Your Square isn't a subtype
of Rectangle by LSP, and I don't know a language that will cal it
a subtype either.

>
> > 2) For any type A and its subtype B, a value (or, in most languages,
> > reference or pointer) of type B is *also* of type A (by
> > definition).
>
> This definition is an equivalent to LSP. It follows from LSP and LSP
> can
> be derived from it.

Of course. LSP is a definition of what is a subtype. When I wrote
"by definition", I meant "by LSP". Statically-typed OO languages
usually try to make the language-specific definition of subtype
match LSP as close as possible.

Probably you have some other definition of subtype. If so,
let's hear about it.


--
Regards
Anatoli (anatoli<at>ptc<dot>com) opinions aren't

Robert C. Martin

unread,
Nov 8, 2000, 3:00:00 AM11/8/00
to

<dmitr...@my-deja.com> wrote in message
news:8ub6v9$6qp$1...@nnrp1.deja.com...

> In article <t0gaiqm...@news.supernews.com>,
> "Robert C. Martin" <rma...@objectmentor.com> wrote:
> >
> > Is that benefit worth the costs? One cost is the lack of mutators in
> > Rectangle. This means that every time you want to change the width or
> > height of the rectangle you must create a new one -- an expensive
> > operation at best.
>
> Maybe, but if it is accepted for Integer<->Float, why should it be
> forbidden for user-defined types?

It's not forbidden, its just not expedient. Most languages make provisions
for the efficient create of primitive types. Creation of non-primitive
types is often expensive. YMMV.

> Note also that advanced compilers (for instance gnat Ada) pass
> relatively large record types rather by copy than by reference. On a
> RISC processor it is more efficient than by-reference passing (which LSP
> tries to enforce in all cases).

In that case, pass by value instead of reference.

> > Another cost is the fact that Square will have two variables within
> > it, when it only requires one. Square will take up twice the space it
> > ought to.
>
> Why so? I would declare:
>
> type Rectangle is record
> Width : Float;
> Height : Float;
> end record;
>
> type Square is record
> Size : Float;
> end record;
>
> Where is extra space? It is LSP (subtyping by extension) that requires
> extra space.

I don't see any inheritance there. Is this ada? How do you assert that a
Square ISA rectangle in this language?

> > A third cost is the high coupling of Square to Rectangle. Every time
> > Rectangle is changed for any reason, Square and its users will likely
> > need recompiling and redeploying.
>
> 1. They are coupled by geometric semantics.

So what? If I change the way we calculate the center point of a rectangle,
I shouldn't have to redeploy Square which has its own, unchanged,
implementation.

> 2. More recompiling and redeploying would be necessary only if you have
> C++ with its comic header files, or if you are trying to inline all
> possible functions. And the situation will not change if you develop
> Square and Rectangle independently.

C++, java, C#, will all experience the redeployment issues. There may be
languages that allow you do change base classes without redeploying
derivatives. Currently they aren't common.

> 3. non-LSP subtyping would also make possible multiple representations.
> Consider:
>
> type Complex_Canonical is ...;
> type Complex_Polar is ...;
>
> Let's make Complex_Polar subtype and supertype of Complex_Canonical.
> After that one can mix instances of both types in all expressions. This
> an example, where LSP fundamentally fails. Because unlike
> Square/Rectange, Complex_Canonical/Polar are "same" without any
> exceptions and still violate LSP.

Sorry, I'm missing the point. What are these classes? What are their
methods? How can Complex_Polar be both a subtype and supertype of
Complex_Canonical? Can you explain?


>
> > Frankly, I don't see the benefit outweighing these costs.
>
> A real cost is that non-LSP subtyping require copy-in/copy-out parameter
> passing. This might be unacceptable in many cases (for obvious reasons).
> So LSP still has its place, but no more than as an implementation
> detail, not as "the flagship of OO technology".

This is just semantics. LSP is a definition of what it means to be a
subtype, no more, no less. Creating subtypes that violate LSP comes with
certain costs. There may also be benfits in some instances. Engineers need
to be able to make the tradeoff.

> >
> > OO is not equivalent to "Square ISA rectangle". OO is a technology
> > for managing interdependencies between modules and structuring source

> > code to be less coupled. It has nothing to do with attempts to make


> > programming languages look like geometry.
>

> This is a view on programming in large. One could have OO in assembler.

Yes, one could.

> But if we are speaking about programming languages then inheritance is
> the native (only) mechanism of subtyping.

In statically typed langauges that is true. In dynamically typed languages
subtyping can be achieved without inheritance. But LSP is still the
definition of subtype in either typing domain.

> If we drop subtyping what
> would remain from OO support in the language?

Subtyping is somethign you can *do* with an OO language. You can do it with
any other kind of language too, but it's a lot harder. The act of creating
abstract supertypes that are implemented by concrete subtypes is the primary
mechanism by which OO programs are created. Such programs have superior
structures because they are strongly decoupled. If you drop subtyping, you
don't have OO. You can drop inheritance, encapsulation, even classes, and
still have OO programs. But if you drop subtyping, no matter what else you
keep, you can't write OO programs. IMHO.

Robert C. Martin

unread,
Nov 8, 2000, 3:00:00 AM11/8/00
to

<sk...@my-deja.com> wrote in message news:8ub7sa$7ic$1...@nnrp1.deja.com...

> In article <t0gaiqm...@news.supernews.com>,
> "Robert C. Martin" <rma...@objectmentor.com> wrote:
>
> > OO is not equivalent to "Square ISA rectangle". OO is a technology
> > for managing interdependencies between modules and structuring source
> > code to be less coupled.
>
> That is the definition of OOP, and parts of OOD.
> It would be a poor, narrow-minded definition of OO as used in s/w
> engineering throughout the lifecycle.

So I've heard; though I've never seen a good argument for this view.

> >It has nothing to do with attempts to make programming languages look
> >like geometry.
>

> The poster from what I see is not attempting to "make programming
> languages look like geometry" . He is discussing what it is in OOP that
> prevents him capturing what is able to be specified fairly easily on
> paper in the geometry problem domain, and still reap the benefits that
> OOP offers (s/w reuse etc) .

Oh, that's easy. Lack of definition. The reason you can capture Square ISA
Rectangle so easily on paper is that you don't have a good definition of
ISA. It means precisely what you intend it to mean, no more, no less.

But when you provide a strong definition for ISA, like the LSP, the ease
with which you can use ISA evaporates.

Robert C. Martin

unread,
Nov 8, 2000, 3:00:00 AM11/8/00
to

<sk...@my-deja.com> wrote in message news:8ub7du$77i$1...@nnrp1.deja.com...
> In article <8u8f76$19$1...@nnrp1.deja.com>,

> You are correct in stating that inheritance is "richer than simply
> subtyping by extension" . The key concepts that inheritance convey are
> generalisation and specialisation, analogy and deviation.

Inheritance has very precise semantics. The relationships you are talking
about are not inheritance; they are something else.

> For the former, there is overlap between it and concepts such as
> subtyping
> and the LSP. For the latter, subtyping fails miserably (and often is the
> cause of analysts/designers with incomplete understanding of OO trying
> to
> use the LSP as a "when all you have is a hammer ..." solution) .

LSP is the definition of a subtype. If you are creating sub<<somthings>>
that don't conform to the LSP, they are not subtypes.

> >>The question is, why is it important for Square to be a subtype of
> >>Rectangle? What benefit does the programmer gain from this?
>
> >1. Code reuse. Methods of Rectangle are inherited by Square. Note,
> >including Resize.
>
> >2. An ability to have lists of rectangles containing squares.
> >3. Generally speaking, to have it OO (:-))
>
> What you are really looking for is for squares to acquire particular
> properties of rectangles, and where those properties are of interest,
> allow
> rectangles and squares to be used interchangeably.
>
> But as you have identified, mutability causes problems.
>
> Analysis of the problem in fact shows that nearly all of the *mutable*
> properties of rectangle are disjoint from the mutable properties of
> square.

We have bottled that analysis and called it the LSP. We can reuse it now,
over and over, without having to do the analysis again. (Though this
newsgroup certainly belies that.)

Owen Rees

unread,
Nov 8, 2000, 7:22:23 PM11/8/00
to
On Tue, 07 Nov 2000 08:50:14 GMT, dmitr...@my-deja.com wrote:

>> Perhaps you are looking for the kind of inheritance that is
>> appropriate to AI knowledge representation. If so, I suggest you read
>> "The Mathematics of Inheritance Systems" by Touretzky. Chapter 2 "A
>> Theory of IS-A Inheritance" presents a generic inheritance system with
>> exceptions.
>>
>> I would advise against using that kind of inheritance to construct
>> relationships between program components; it does not generate a
>> subtype relation that is useful in type checking of programs.
>

>Surely it does. When you write Put(Float(123)); it is pretty type safe.
>It remains safe if 123 is converted to Float implicitly: Put(123);

It is not sufficient to show that one example works. For a subtype
relation between types to be useful for type checking, you must show
that the relation has the property you want for every pair of types,
and all values of those types. You will also find that you need to
show that the relation is reflexive and transitive.

The exact nature of the relation depends on what you expect to have
learned when you establish that one type is a subtype of another. If
you want know that a member of the subtype can be used wherever a
member of the supertype is expected without affecting the behaviour in
places where only the supertype is known then you end up with LSP. If
you merely want to know that you will not get "method not understood"
errors then you can get away with the weaker signature conformance
subtype relation, but you have to deal with any behavioural anomalies
that may arise.

dmitr...@my-deja.com

unread,
Nov 9, 2000, 3:00:00 AM11/9/00
to
In article <8ubtv0$ovt$1...@nnrp1.deja.com>,

def. in-subtype. A is an in-subtype of B, if an actual of A can be
converted to a formal of B.

[consequence: A inherits (can be used with) in-methods of B]

def. out-subtype. A is an out-subtype of B, if a formal of B can be
converted to an actual of A.

[consequence: A inherits out-methods of B]

def. inout-subtype. A is simultaneously an in-subtype and an out-subtype
of B. I.e. there are forward A->B and backward A<-B conversions.

[consequence: A inherits all methods of B]

These definitions cover LSP subtyping: subtype is a LSP-subtype if

1. It is an inout-subtype;
2. Parameters of B methods are passed by reference;
3. A->B and B<-A conversions are reference shifts.

According to these definitions 'int' is an in-subtype of 'double' in
C++, which is an example of existence of non-LSP subtyping in
programming languages.

--
Regards,
Dmitry Kazakov

dmitr...@my-deja.com

unread,
Nov 9, 2000, 3:00:00 AM11/9/00
to
In article <t0j364k...@news.supernews.com>,

"Robert C. Martin" <rma...@objectmentor.com> wrote:
>
> <dmitr...@my-deja.com> wrote in message
> news:8ub6v9$6qp$1...@nnrp1.deja.com...
> > In article <t0gaiqm...@news.supernews.com>,
> > "Robert C. Martin" <rma...@objectmentor.com> wrote:

> > Why so? I would declare:
> >
> > type Rectangle is record
> > Width : Float;
> > Height : Float;
> > end record;
> >
> > type Square is record
> > Size : Float;
> > end record;
> >
> > Where is extra space? It is LSP (subtyping by extension) that
> > requires extra space.
>
> I don't see any inheritance there. Is this ada?
> How do you assert that a
> Square ISA rectangle in this language?

Inheritance isn't shown here (one should invent a corresponding syntax
construct). Unfortunately Ada follows LSP. However it allows by-copy
parameter passing for some subtypes.

To make Square a subtype of Rectangle one should define appropritate
conversion functions. For LSP subtyping the compiler can do it (and
does) implicitly. For non-LSP subtyping the programmer should do it:

-- This is not Ada!
--
-- Forward conversion makes Square an in-subtype of Rectangle
--
forward conversion (X : in Square) return Rectangle is
begin
return Rectangle'(Width => X.Size, Height => X.Size);
end;
--
-- Backward conversion makes Square an out-subtype of Rectangle
--
backward conversion (X : in Rectangle) return Square is
begin
if X.Width /= X.Height then
raise This_Is_Not_A_Square;
else
return Square'(Size => X.Height);
end if;
end;

> > 1. They are coupled by geometric semantics.
>
> So what? If I change the way we calculate the center point of a
> rectangle, I shouldn't have to redeploy Square which has its own,
> unchanged, implementation.

Formally yes, but such Square would be rather useless. Surely there
would be plethora of methods that reflect the geometric fact that square
is a rectangle. These methods would be subject of modifications.

> > 3. non-LSP subtyping would also make possible multiple
> > representations. Consider:
> >
> > type Complex_Canonical is ...;
> > type Complex_Polar is ...;
> >
> > Let's make Complex_Polar subtype and supertype of Complex_Canonical.
> > After that one can mix instances of both types in all expressions.
> > This an example, where LSP fundamentally fails. Because unlike
> > Square/Rectange, Complex_Canonical/Polar are "same" without any
> > exceptions and still violate LSP.
>
> Sorry, I'm missing the point. What are these classes?

They represent complex numbers:

type Complex_Canonical is record
Real : Float;
Img : Float;
end record;

subtype Magnitude is Float range 0.0..Float'Last;
subtype Angle is Float range 0.0..2.0*PI;

type Complex_Polar is record
P : Magnitude;
A : Angle;
end record;

> What are their methods?

For instance, +,-,*,/ etc.

> How can Complex_Polar be both a subtype and supertype of
> Complex_Canonical? Can you explain?

Let's Complex_Polar does not have +/-. Then it would be nice to make it
a subtype of Complex_Canonical, i.e. to implement two conversions:

P_to_C = actual Complex_Polar to formal Complex_Canonical
P_from_C = formal Complex_Canonical to actual Complex_Polar

This would allow to write:

X, Y : Complex_Polar;
...
X:=X+Y; -- '+' is inherited from Complex_Canonical
--
-- translated to X := P_from_C (P_to_C (X) + P_to_C (Y));
--
Then if Complex_Polar has a nice Put method that prints complex numbers
in A*e+j*B format we could make it accessible for Complex_Canonical by
supertyping. To have this we make Complex_Polar a supertype of
Complex_Canonical by defining another pair of conversion functions:

C_from_P = actual Complex_Canonical to formal Complex_Polar
C_to_P = formal Complex_Polar to actual Complex_Canonical

This would export Complex_Polar.Put to Complex_Canonical.

Z : Complex_Canonical;
...
Z.Put; -- Put is exported by Complex_Polar
--
-- translated to Put (C_to_P (Z));
--


> > But if we are speaking about programming languages then inheritance
> > is the native (only) mechanism of subtyping.
>
> In statically typed langauges that is true. In dynamically typed
> languages subtyping can be achieved without inheritance.

I do not see how statical indeterminability of types would
help to implement subtyping. But this is another story (:-))

> But LSP is still the definition of subtype in either typing domain.
>
> > If we drop subtyping what
> > would remain from OO support in the language?
>
> Subtyping is somethign you can *do* with an OO language. You can do
> it with any other kind of language too, but it's a lot harder. The
> act of creating abstract supertypes that are implemented by concrete
> subtypes is the primary mechanism by which OO programs are created.
> Such programs have superior structures because they are strongly
> decoupled. If you drop subtyping, you don't have OO. You can drop
> inheritance, encapsulation, even classes, and still have OO programs.
> But if you drop subtyping, no matter what else you keep, you can't
> write OO programs. IMHO.

Agreed.

sk...@my-deja.com

unread,
Nov 9, 2000, 3:00:00 AM11/9/00
to
In article <t0j42l...@news.supernews.com>,

"Robert C. Martin" <rma...@objectmentor.com> wrote:

> <sk...@my-deja.com> wrote in message
news:8ub7du$77i$1...@nnrp1.deja.com...
> > In article <8u8f76$19$1...@nnrp1.deja.com>,
>
> > You are correct in stating that inheritance is "richer than simply
> > subtyping by extension" . The key concepts that inheritance convey
are
> > generalisation and specialisation, analogy and deviation.
>
> Inheritance has very precise semantics. The relationships you are
> talking about are not inheritance; they are something else.

Afraid not. The concepts I talk of are those defined by people such as
Peter Wegner to define themes and concepts behind inheritance (cf
Research directions in Object-Oriented Programming - pub 1987) .

And tie in with the notions of folks like the progenitors of Simula.

Similarly, I challenge you to cite your references where one can find
these places where the "precise semantics" of inheritance are defined.

> > What you are really looking for is for squares to acquire particular
> > properties of rectangles, and where those properties are of
> > interest, allow
> > rectangles and squares to be used interchangeably.

> > But as you have identified, mutability causes problems.

> > Analysis of the problem in fact shows that nearly all of the
> > *mutable* properties of rectangle are disjoint from the mutable
> > properties of square.

> We have bottled that analysis and called it the LSP.
> We can reuse it now, over and over, without having to do the
> analysis again.

Absolutely meaningless statements IMHO (anyone else able to decipher
it ?? :-) ) . Precise semantics do seem to fall by the wayside when
you get going (sadly) ...


Regards,
Steven Perryman

sk...@my-deja.com

unread,
Nov 9, 2000, 3:00:00 AM11/9/00
to
In article <8ub6v9$6qp$1...@nnrp1.deja.com>,

dmitr...@my-deja.com wrote:
>So LSP still has its place, but no more than as an implementation
>detail, not as "the flagship of OO technology".

There is an "OO = LSP" mindset among people.
Perpetuating this mindset is what IMHO causes a lot of analysis/design
problems (courtesy of the LSP becoming the "when all you have is a
hammer ..." solution) .

However, the LSP is more than a mere "implementation detail" , and is
applicable across all the s/w eng activities in which OO is used.
I wouldn't relegate LSP to such an extent.

>But if we are speaking about programming languages then inheritance is
>the native (only) mechanism of subtyping.

Inheritance can be used when one seeks to represent subtyping in OOP.

When the Simula progenitors devised inheritance, AFAIK it was not
intended to provide a mechanism that mirrored the mathematically
rigorous concept of subtyping. That inheritance can be used to apply
subtyping concepts, was probably an incidental (though very beneficial)
side-effect.

As for prog langs in general, inheritance is not the "native" mechanism
of subtyping. For example, many functional prog langs have very
elegant/powerful mechanisms for defining subtyping that have nothing
to do with inheritance in the OO sense.

>If we drop subtyping what would remain from OO support in the language?

How does one "drop" subtyping from OOP ??
Subtyping is just a usage style. Therefore one would merely be saying
they are not going to conform to or use the style.

In OOP, without subtyping, you would still have virtual functions.
Thus you still have specialisation and generalisation, analogy and
deviation.

But by ignoring the concept of subtyping, certain correctness
opportunities are lost to you (as shown by a particular use of the
"list of rectangles" scenario) . Whether this is a price you can pay
(the scenario will never occur in your s/w etc) , only you can decide.

Experienced engineers generally have no problem with such decisions,
as long as you have documented and shown awareness of the potential
consequences thereof. That is what the real world is about.

sk...@my-deja.com

unread,
Nov 9, 2000, 3:00:00 AM11/9/00
to
In article <3A07FF42...@dmu.ac.uk>,

Graham Perkins <g...@dmu.ac.uk> wrote:
>Well, if you're allowed to talk about the intended type of a variable
>rather than its declared type, then I'll grant that LSP is relevant to
>dynamic typed languages. But I do say *TYPE*, not class. And it's quite
>clear that the Smalltalk class hierarchy is not a type hierarchy!
>Maybe much of it is, but there are very many places where a subclass is
>not a subtype, also many cases of subtype not a subclass.

Ah, subclass and subtype (sigh :-) ) .

Was there not quite a lot of debate along the "ISA is not subclass" ,
"subtype is not subclass" lines in the late 1980s ??

I recall that a fair number of articles etc were pretty good indeed.

Perhaps the best treatises of the time need to be made available here
to one and all to correct some of the misunderstandings still lingering
a decade later ...

dmitr...@my-deja.com

unread,
Nov 9, 2000, 3:00:00 AM11/9/00
to
In article <3a09e31...@news.waitrose.com>,

owen...@waitrose.deletethis.com (Owen Rees) wrote:
> On Tue, 07 Nov 2000 08:50:14 GMT, dmitr...@my-deja.com wrote:
>
> >In article <3a074d61...@news.waitrose.com>,
> > owen...@waitrose.deletethis.com (Owen Rees) wrote:
> >> Perhaps you are looking for the kind of inheritance that is
> >> appropriate to AI knowledge representation. If so, I suggest you
> >> read "The Mathematics of Inheritance Systems" by Touretzky. Chapter
> >> 2 "A Theory of IS-A Inheritance" presents a generic inheritance
> >> system with exceptions.
> >>
> >> I would advise against using that kind of inheritance to construct
> >> relationships between program components; it does not generate a
> >> subtype relation that is useful in type checking of programs.
> >
> >Surely it does. When you write Put(Float(123)); it is pretty type
> >safe. It remains safe if 123 is converted to Float implicitly:
> >Put(123);
>
> It is not sufficient to show that one example works. For a subtype
> relation between types to be useful for type checking, you must show
> that the relation has the property you want for every pair of types,
> and all values of those types.

Whether the fact of being a subtype can be statically detected or not,
does not depend on the subtype representation.

> You will also find that you need to
> show that the relation is reflexive and transitive.

OK. If the subtype relation is defined as "for each A there is a
conversion to B". Then it is reflexive (each x can be converted to x by
doing nothing) and transitive (if x can be converted to y and y can be
converted to z, then obviously x can be converted to z).

> The exact nature of the relation depends on what you expect to have
> learned when you establish that one type is a subtype of another. If
> you want know that a member of the subtype can be used wherever a
> member of the supertype is expected without affecting the behaviour in
> places where only the supertype is known then you end up with LSP.

Why so? Again if an instance of subtype is converted to an instance of
supertype then any method of supertype can be applied to the result.
LSP just imposes some restrictions upon the nature of that conversion
(shall be a reference shift). I propose to allow any conversions.

> If you merely want to know that you will not get "method not
> understood" errors then you can get away with the weaker signature
> conformance subtype relation, but you have to deal with any
> behavioural anomalies that may arise.

Surely sqrt is a method of Float, however anomalies still arise if the
argument is negative. It is silly to hope that it is possible to have an
operation defined for all instances of a type. If this cannot be
achieved even for the base type, why should we require it from its subtypes?

--
Regards,
Dmitry Kazakov

Anatoli Tubman

unread,
Nov 9, 2000, 3:00:00 AM11/9/00
to
In article <8udmin$8ut$1...@nnrp1.deja.com>,
dmitr...@my-deja.com wrote:

> def. in-subtype. A is an in-subtype of B, if an actual of A can be
> converted to a formal of B.
>
> [consequence: A inherits (can be used with) in-methods of B]
>
> def. out-subtype. A is an out-subtype of B, if a formal of B can be
> converted to an actual of A.
>
> [consequence: A inherits out-methods of B]
>
> def. inout-subtype. A is simultaneously an in-subtype and an
> out-subtype
> of B. I.e. there are forward A->B and backward A<-B conversions.
>
> [consequence: A inherits all methods of B]
>
> These definitions cover LSP subtyping: subtype is a LSP-subtype if
>
> 1. It is an inout-subtype;
> 2. Parameters of B methods are passed by reference;
> 3. A->B and B<-A conversions are reference shifts.
>
> According to these definitions 'int' is an in-subtype of 'double' in
> C++, which is an example of existence of non-LSP subtyping in
> programming languages.

I can't make heads nor tails out of it.

1) What is an in-method?
2) What is an out-method?
3) You write "A->B and A<-B" in one place, and "A->B and B<-A" in
another place, what do you actually mean?
3a) If you mean "A->B and A<-B", do you also say that you can
convert both CAR to VEHICLE and VEHICLE to CAR?
3b) If you mean "A->B and B<-A", please explain the difference
between A->B and B<-A.
4) What is a reference shift, and why passing by reference is
important?
5) What significance formals and actuals have?

For your information, here's my approximation of the
"classic" LSP.

Type A is said to be a subtype of B when for every program P
accepting parameter x the following holds:
((x has type B) implies (P is correct)) implies
((x has type A) implies (P is correct))

This definition is relative to one's definition of correctness.
Usually programming languages assume "correctness" == "freedom of
runtime type errors". One can also include pre/post conditions
and invariants in (Eiffel).

Note that we don't talk about methods of A and B here, only
of programs that accept A and B.

As one can see, this definition is almost a tautology. We allow
substitutions which are "correct" (i.e. A->B) and disallow those which
are not guaranteed to be correct. Please note also that substitutions
include not only argument passing but also simple assignment.

And, of course, the big question: *why would anybody want it
any other way*?

--
Regards
Anatoli (anatoli<at>ptc<dot>com) opinions aren't

Owen Rees

unread,
Nov 10, 2000, 3:00:00 AM11/10/00
to
On Thu, 09 Nov 2000 09:59:29 GMT, dmitr...@my-deja.com wrote:

>In article <3a09e31...@news.waitrose.com>,


> owen...@waitrose.deletethis.com (Owen Rees) wrote:
>> It is not sufficient to show that one example works. For a subtype
>> relation between types to be useful for type checking, you must show
>> that the relation has the property you want for every pair of types,
>> and all values of those types.
>

>Whether the fact of being a subtype can be statically detected or not,
>does not depend on the subtype representation.

My point applies just as much to dynamic as to static type checking,
and has nothing to do with representation. For a subtype relation to
be useful, it must be well-defined.


>
>> You will also find that you need to
>> show that the relation is reflexive and transitive.
>

>OK. If the subtype relation is defined as "for each A there is a
>conversion to B". Then it is reflexive (each x can be converted to x by
>doing nothing) and transitive (if x can be converted to y and y can be
>converted to z, then obviously x can be converted to z).

Now we have at least a partial definition of what you mean by subtype;
it seems to me to be something like what was called coercion in
Algol68, and the complexity of the coercion rules for Algol68 should
be a hint that maybe things are not as simple as you hope.

I find "can be converted to" an unsatisfactory definition of subtype,
and of little apparent value. If you are not concerned with how much
information you lose (and you said nothing about that), then there is
at least one conversion of every type to every other simply by picking
some value of each type to which all values of all other types are
converted. This is useless, and so I assume that there are some
unstated assumptions that limit the kind of conversions you will
allow.

If we require that the conversion is a total function - i.e. for every
x of type X there is a unique y of type Y to which it converts, then
we do not necessarily have transitivity(1). If the conversion is a
function but not total then we do not necessarily have
transitivity(2). If the conversion is not a function (or we allow more
than one conversion function), then we will have interesting
non-deterministic behaviour in our programs.

(1) suppose we have a total functions fxy: X->Y, fyz Y->Z, fxw: X->W
and fwz: W->Z. fyz(fxy(x)) is not necessarily equal to fwz(fxw(x)) for
all x of type X, therefore there is no function fxz: X->Z which we can
choose as the conversion from X to Z such that it is equivalent to
conversions via both W and Y.

(2) If the functions are not required to be total then we have the
simpler possibility that for every x of type X, fyz is not defined for
fxy(x), so there is no z of type Z to which any x converts by this
route.

>
>> The exact nature of the relation depends on what you expect to have
>> learned when you establish that one type is a subtype of another. If
>> you want know that a member of the subtype can be used wherever a
>> member of the supertype is expected without affecting the behaviour in
>> places where only the supertype is known then you end up with LSP.
>

>Why so? Again if an instance of subtype is converted to an instance of
>supertype then any method of supertype can be applied to the result.
>LSP just imposes some restrictions upon the nature of that conversion
>(shall be a reference shift). I propose to allow any conversions.

I intended my statement to be a somewhat less formal rendering of
"properties that can be proved using the specification of an object's
presumed type should hold even though the object is actually a member
of a subtype of that type." You will find that on page 1812 of ACM
Transactions on Programming Languages and Systems, Vol. 16 No. 6,
November 1994 in a paper called "A Behavioural Notion of Sybtyping"
which I believe to be relevant to the definition of what has come to
be known as LSP.

Perhaps you could give me a reference to where "reference shift" is
defined, and where it is used in defining LSP?

>
>> If you merely want to know that you will not get "method not
>> understood" errors then you can get away with the weaker signature
>> conformance subtype relation, but you have to deal with any
>> behavioural anomalies that may arise.
>

>Surely sqrt is a method of Float, however anomalies still arise if the
>argument is negative. It is silly to hope that it is possible to have an
>operation defined for all instances of a type. If this cannot be
>achieved even for the base type, why should we require it from its subtypes?

I would expect the behaviour of sqrt to be well defined when presented
with a negative argument; for example, in a language with exceptions I
would expect there to be some specified exception thrown in this case.
This is not anomalous behaviour.

Owen Rees

unread,
Nov 10, 2000, 3:00:00 AM11/10/00
to
On Thu, 09 Nov 2000 09:56:41 GMT, sk...@my-deja.com wrote:

>Ah, subclass and subtype (sigh :-) ) .
>
>Was there not quite a lot of debate along the "ISA is not subclass" ,
>"subtype is not subclass" lines in the late 1980s ??
>
>I recall that a fair number of articles etc were pretty good indeed.
>
>Perhaps the best treatises of the time need to be made available here
>to one and all to correct some of the misunderstandings still lingering
>a decade later ...

You may be thinking of papers like "Inheritance is not Subtyping" by
Cook, Hill and Canning - unfortunately, as far as I know, it is not
available online (at least, not free), nor do I have the full
citation.

sk...@my-deja.com

unread,
Nov 10, 2000, 3:00:00 AM11/10/00
to
In article <t0j3g5b...@news.supernews.com>,

"Robert C. Martin" <rma...@objectmentor.com> wrote:

> <sk...@my-deja.com> wrote in message
news:8ub7sa$7ic$1...@nnrp1.deja.com...

> > That is the definition of OOP, and parts of OOD.
> > It would be a poor, narrow-minded definition of OO as used in s/w
> > engineering throughout the lifecycle.
>
> So I've heard; though I've never seen a good argument for this view.

I've seen many. Experienced them first-hand in the real world.
But then as Ed Berard often tells you, perhaps your experiences in
the real world are different to those of others.

Fortunately my experience means I never end up reanalysing the wheel
in working on similar systems.

> > The poster from what I see is not attempting to "make programming
> > languages look like geometry" . He is discussing what it is in OOP
> > that prevents him capturing what is able to be specified fairly
> > easily on paper in the geometry problem domain, and still reap the
> > benefits that OOP offers (s/w reuse etc) .

> Oh, that's easy. Lack of definition. The reason you can capture
> Square ISA Rectangle so easily on paper is that you don't have a good
> definition of ISA.

> It means precisely what you intend it to mean, no more, no less.

Well actually, Dimitry may find some solace in the fact that Wegner
suggests that the subset relation is more strongly akin to ISA then
other definitions. Tying in nicely with the fact for square/rectangle,
the subtype relationship is a subset relation.

> But when you provide a strong definition for ISA, like the LSP, the
> ease with which you can use ISA evaporates.

So with ISA = subset in mind :

The Wegner stuff predicts very well what would happen with trying to
use the LSP to solve square/rectangle. It also predicts where the
likely subtype compatibility would be in terms of substitutibility,
and his comments on the blind adherence to the LSP in order to gain
some notion of "correctness" in terms of effort expended were again
spot on (as I found with the "total" vs "partial" compatibility
subtype solutions I devised for the problem) .

Recommended read (for those interested) :

Wegner and Zdonik : "Inheritance as an incremental mechanism ..."
Pub 1988. AFAIK in the ECOOP 88 proceedings at least.


Regards,
Steven Perryman

dmitr...@my-deja.com

unread,
Nov 10, 2000, 3:00:00 AM11/10/00
to
In article <8ue546$k6b$1...@nnrp1.deja.com>,

Anatoli Tubman <ana...@my-deja.com> wrote:
> In article <8udmin$8ut$1...@nnrp1.deja.com>,
> dmitr...@my-deja.com wrote:
>
> > def. in-subtype. A is an in-subtype of B, if an actual of A can be
> > converted to a formal of B.
> >
> > [consequence: A inherits (can be used with) in-methods of B]
> >
> > def. out-subtype. A is an out-subtype of B, if a formal of B can be
> > converted to an actual of A.
> >
> > [consequence: A inherits out-methods of B]
> >
> > def. inout-subtype. A is simultaneously an in-subtype and an
> > out-subtype
> > of B. I.e. there are forward A->B and backward A<-B conversions.
> >
> > [consequence: A inherits all methods of B]
> >
> > These definitions cover LSP subtyping: subtype is a LSP-subtype if
> >
> > 1. It is an inout-subtype;
> > 2. Parameters of B methods are passed by reference;
> > 3. A->B and B<-A conversions are reference shifts.
> >
> > According to these definitions 'int' is an in-subtype of 'double' in
> > C++, which is an example of existence of non-LSP subtyping in
> > programming languages.
>
> I can't make heads nor tails out of it.
>
> 1) What is an in-method?

A subprogram that has at least one parameter of the type passed in
IN-mode. sqrt could be an in-method of double.

[ perhaps it is better to reserve term the method for dispatching
subprograms only ]

> 2) What is an out-method?

A subprogram that has at least one parameter of the type passed in OUT-mode:

procedure Read (File : in out Stream; Data : out A);

could be an out-method of A.

> 3) You write "A->B and A<-B" in one place, and "A->B and B<-A" in
> another place, what do you actually mean?

Opps. Sorry, should be always A<-B.

> 3a) If you mean "A->B and A<-B", do you also say that you can
> convert both CAR to VEHICLE and VEHICLE to CAR?

Yes. One need both conversions for inout-methods.

> 3b) If you mean "A->B and B<-A", please explain the difference
> between A->B and B<-A.

That was a typing error. But the difference exists. If you make two
types "same", i.e. if B is an inout-subtype of A and simultaneously its
inout-supertype, then there should be four conversions:

1.1. actual B to formal A: B inherits in-methods (subtyping)
1.2. formal A to actual B: B inherits out-methods (subtyping)
2.1. actual A to formal B: B exports in-methods to A (supertyping)
2.2. formal B to actual A: B exports out-methods to A (supertyping)

For non-LSP case it is hard to find why it could be necessare to have
different A->B & B<-A, and B->A & A<-B.

For LSP they cannot be same. For instance A->B could be implemented as a
reference shift, while B<-A should always construct a new B using a
value of A. However, LSP supertyping seems to be rather useless.

> 4) What is a reference shift, and why passing by reference is
> important?

LSP, or subtyping by extension, as it implemented in all languages I
know, requires:

1. Representation of B contains representation of A
2. When B is passed as A, then the reference to B is shifted to the
corresponding part of A. Upon return it is shifted back.

If LSP in your interpretation does not require 1.& 2., then I would have
nothing against such kind of LSP.

> 5) What significance formals and actuals have?

Sorry?

> For your information, here's my approximation of the
> "classic" LSP.
>
> Type A is said to be a subtype of B when for every program P
> accepting parameter x the following holds:
> ((x has type B) implies (P is correct)) implies
> ((x has type A) implies (P is correct))
>
> This definition is relative to one's definition of correctness.
> Usually programming languages assume "correctness" == "freedom of
> runtime type errors". One can also include pre/post conditions
> and invariants in (Eiffel).

It is pretty compatible with my definition. Here nothing is said about
whether A should have B as a part.

> Note that we don't talk about methods of A and B here, only
> of programs that accept A and B.

Indeed. But we should somehow distinguish:

1. Subprograms that are never inherited (such as class-wide methods).
2. Non-dispatching subprograms
3. Dispatching subprograms (usually called methods)

> As one can see, this definition is almost a tautology. We allow
> substitutions which are "correct" (i.e. A->B) and disallow those which
> are not guaranteed to be correct. Please note also that substitutions
> include not only argument passing but also simple assignment.

There is nothing special in assignment. Its left argument is passed in
INOUT-mode [or OUT-mode] (so you need both A->B and A<-B). Assignment
would surely remain correct if both conversions are present. This way
one can assign a Rectangle to some Square. First Square is converted to
a Rectangle (copy-in), then this rectangle is given a new value
(assingment of Rectangles), then the result is converted back (copy-out).

> And, of course, the big question: *why would anybody want it
> any other way*?

Were is other way? If you argee that subtyping is about interface, not
implementation, then it is the same way (:-)).

--
Regards,
Dmitry Kazakov

dmitr...@my-deja.com

unread,
Nov 10, 2000, 3:00:00 AM11/10/00
to
In article <3a0b362a...@news.waitrose.com>,
owen...@waitrose.deletethis.com (Owen Rees) wrote:
> On Thu, 09 Nov 2000 09:59:29 GMT, dmitr...@my-deja.com wrote:
>
> >In article <3a09e31...@news.waitrose.com>,
> > owen...@waitrose.deletethis.com (Owen Rees) wrote:

> Now we have at least a partial definition of what you mean by subtype;
> it seems to me to be something like what was called coercion in
> Algol68, and the complexity of the coercion rules for Algol68 should
> be a hint that maybe things are not as simple as you hope.

subtype Positive is Integer range 1..Integer'Last;

It is Ada 83 and Ada is a very carefully designed language.

> I find "can be converted to" an unsatisfactory definition of subtype,
> and of little apparent value. If you are not concerned with how much
> information you lose (and you said nothing about that), then there is
> at least one conversion of every type to every other simply by picking
> some value of each type to which all values of all other types are
> converted. This is useless,

Existence of a conversion means that the programmer (explicitly or
implicitly) has defined one.

> and so I assume that there are some unstated assumptions that limit
> the kind of conversions you will allow.

Yes, only the intentionally defined ones.

> If we require that the conversion is a total function - i.e. for every
> x of type X there is a unique y of type Y to which it converts, then
> we do not necessarily have transitivity(1). If the conversion is a
> function but not total then we do not necessarily have
> transitivity(2). If the conversion is not a function (or we allow more
> than one conversion function), then we will have interesting
> non-deterministic behaviour in our programs.
>
> (1) suppose we have a total functions fxy: X->Y, fyz Y->Z, fxw: X->W
> and fwz: W->Z. fyz(fxy(x)) is not necessarily equal to fwz(fxw(x)) for
> all x of type X, therefore there is no function fxz: X->Z which we can
> choose as the conversion from X to Z such that it is equivalent to
> conversions via both W and Y.

This should cause a compilation error if neither of conversion paths
was not made preferable (like C++ does in case 1+1.0). Languages are
full of such or similar problems:

class A
{
public :
void Foo ();
};
class B : public A {};
class C : public A {};
class D : public B, public C {};

In full conformace with LSP (:-)),

D X;

X.Foo (); -- Compilation error

> (2) If the functions are not required to be total then we have the
> simpler possibility that for every x of type X, fyz is not defined for
> fxy(x), so there is no z of type Z to which any x converts by this
> route.

This should cause a run-time error (in worst case). Example:

int I = 1e37;

> Perhaps you could give me a reference to where "reference shift" is
> defined, and where it is used in defining LSP?

This is how it is implemented in C++, Ada 95 etc. and this is a direct
consequence of the statement "if B is a subtype of A, then each instance
of B contains an instance of A". Is it in your opinion a right way to
implement LSP as you understand it?

> >> If you merely want to know that you will not get "method not
> >> understood" errors then you can get away with the weaker signature
> >> conformance subtype relation, but you have to deal with any
> >> behavioural anomalies that may arise.
> >

> >Surely sqrt is a method of Float, however anomalies still arise if
> >the argument is negative. It is silly to hope that it is possible to
> >have an operation defined for all instances of a type. If this cannot
> >be achieved even for the base type, why should we require it from its
> >subtypes?
>
> I would expect the behaviour of sqrt to be well defined when presented
> with a negative argument; for example, in a language with exceptions I
> would expect there to be some specified exception thrown in this case.
> This is not anomalous behaviour.

Similarily, not an anomalous behaviour is raising an exception in:

X : Positive := Decrement (1);

or

X : Square;
...
Resize (X, 10.0, 20.0);

dmitr...@my-deja.com

unread,
Nov 10, 2000, 3:00:00 AM11/10/00
to
In article <8uds4j$dml$1...@nnrp1.deja.com>,
sk...@my-deja.com wrote:

> As for prog langs in general, inheritance is not the "native"
> mechanism of subtyping. For example, many functional prog langs have
> very elegant/powerful mechanisms for defining subtyping that have
> nothing to do with inheritance in the OO sense.

IMO, inheritance includes subtyping. A subtype inherits at least the
interface of the base type. However, inheritance is more than subtyping,
like in:

type Height is new Width;

> >If we drop subtyping what would remain from OO support in the
> >language?
>
> How does one "drop" subtyping from OOP ??
> Subtyping is just a usage style. Therefore one would merely be saying
> they are not going to conform to or use the style.
>
> In OOP, without subtyping, you would still have virtual functions.

What use of dispatching subprograms if they are not inherited?

> Thus you still have specialisation and generalisation, analogy and
> deviation.

How it is translated into language constructs?

sk...@my-deja.com

unread,
Nov 13, 2000, 3:00:00 AM11/13/00
to
In article <8ugubp$tnk$1...@nnrp1.deja.com>,

dmitr...@my-deja.com wrote:
>In article <8uds4j$dml$1...@nnrp1.deja.com>,
> sk...@my-deja.com wrote:

>> As for prog langs in general, inheritance is not the "native"
>> mechanism of subtyping. For example, many functional prog langs have
>> very elegant/powerful mechanisms for defining subtyping that have
>> nothing to do with inheritance in the OO sense.

>IMO, inheritance includes subtyping. A subtype inherits at least the
>interface of the base type.

Inheritance does indeed have a relationship to subtyping.

The point I wished to make is that independently of the OOP arena, there
are areas where subtyping has been addressed without use of the
inheritance concept which underpins OOP (although the results may be
considered to be akin to inheritance in some cases) .

>However, inheritance is more than subtyping

Agreed.

> >If we drop subtyping what would remain from OO support in the
> >language?

>> How does one "drop" subtyping from OOP ??
>> Subtyping is just a usage style. Therefore one would merely be saying
>> they are not going to conform to or use the style.

>> In OOP, without subtyping, you would still have virtual functions.

>What use of dispatching subprograms if they are not inherited?

>> Thus you still have specialisation and generalisation, analogy and
>> deviation.

Dmitry, I apologise for my misunderstanding of your immediate comments
on "drop subtyping" . Where I have put "without subtyping" , I really
meant to say "without subtype substitutability" . My comments should
now be consistent in that context (hopefully :-) ) .


Regards,
Steven Perryman

sk...@my-deja.com

unread,
Nov 13, 2000, 3:00:00 AM11/13/00
to
In article <8u3ir2$6q5$1...@plutonium.compulink.co.uk>,
bran...@cix.compulink.co.uk wrote:

>dani...@my-deja.com () wrote (abridged):
>> Eiffel doesn't have private inheritenace.

>Eiffel has selective export, which you can use to "hide" a feature.
>The result is similar to private inheritance; it means that the
>subclass no longer conforms to the base class from the point of view
>of anyone using the hidden feature. It does conform if you don't use
>the feature. This goes against Liskov but is arguably what you want. A
>square is a rectangle as long as you don't want to resize it.

That appears to be IMHO a quite succinct way of supporting the "analogy/
deviation" concept that inheritance brings to OO.

I am interested in the outcome if a square is referenced as a rectangle
and the resize op is invoked. Does the Eiffel run-time raise a std
exception (no such op etc) ??

If so, this at least allows the developer to realise that a square is
not completely compatible with a rectangle (Wegner defines subtype
substitutability in terms of "complete" compatibility - which is a
very strict form of subtyping but not the overall definition of
subtyping) . Contrast this with a solution in C++ etc, where the effect
of this kind of problem could be much more subtle and the cause harder
to locate. :-(

bran...@cix.compulink.co.uk

unread,
Nov 13, 2000, 3:00:00 AM11/13/00
to
sk...@my-deja.com () wrote (abridged):

> I am interested in the outcome if a square is referenced as a rectangle
> and the resize op is invoked. Does the Eiffel run-time raise a std
> exception (no such op etc) ??

It's hard to tell what Eiffel really does. "Object Oriented Software
Construction", 2nd Ed, chapter 17, discusses several approaches, some of
which need global analysis. I believe early compilers tried that and got
it wrong, and could be made to crash. OOSC suggests a rule which is more
pessimistic but easier to implement, but says it has not yet been
implemented.


> If so, this at least allows the developer to realise that a square is
> not completely compatible with a rectangle (Wegner defines subtype
> substitutability in terms of "complete" compatibility - which is a
> very strict form of subtyping but not the overall definition of
> subtyping) . Contrast this with a solution in C++ etc, where the effect
> of this kind of problem could be much more subtle and the cause harder
> to locate. :-(

In C++ we can override the resize routine to raise a runtime error
manually. (We can do this in Eiffel too, of course.)

Dave Harris, Nottingham, UK | "Weave a circle round him thrice,
bran...@cix.co.uk | And close your eyes with holy dread,
| For he on honey dew hath fed
http://www.bhresearch.co.uk/ | And drunk the milk of Paradise."

Owen Rees

unread,
Nov 13, 2000, 7:53:26 PM11/13/00
to
On Fri, 10 Nov 2000 13:24:50 GMT, dmitr...@my-deja.com wrote:

>In article <3a0b362a...@news.waitrose.com>,
> owen...@waitrose.deletethis.com (Owen Rees) wrote:
>> On Thu, 09 Nov 2000 09:59:29 GMT, dmitr...@my-deja.com wrote:
>>
>> >In article <3a09e31...@news.waitrose.com>,


>> > owen...@waitrose.deletethis.com (Owen Rees) wrote:
>
>> Now we have at least a partial definition of what you mean by subtype;
>> it seems to me to be something like what was called coercion in
>> Algol68, and the complexity of the coercion rules for Algol68 should
>> be a hint that maybe things are not as simple as you hope.
>
>subtype Positive is Integer range 1..Integer'Last;
>
>It is Ada 83 and Ada is a very carefully designed language.

I am afraid that although I know an unnecessarily large number of
programming languages, Ada is not one of them. This makes it difficult
for me to understand what point you are making. If you are saying that
your definition of "subtype" is the Ada definition, then it would be
easier to just say so.

>
>> I find "can be converted to" an unsatisfactory definition of subtype,
>> and of little apparent value. If you are not concerned with how much
>> information you lose (and you said nothing about that), then there is
>> at least one conversion of every type to every other simply by picking
>> some value of each type to which all values of all other types are
>> converted. This is useless,
>
>Existence of a conversion means that the programmer (explicitly or
>implicitly) has defined one.

I understand, and have used, explicitly defined conversions, but you
will have to explain what you mean by an implicitly defined
conversion.

>
>> and so I assume that there are some unstated assumptions that limit
>> the kind of conversions you will allow.
>
>Yes, only the intentionally defined ones.

Would I be right in restating your concept of subtype as "there is an
intentionally defined conversion to" rather than "can be converted
to"? This seems to be what you have just said.

>
>> If we require that the conversion is a total function - i.e. for every
>> x of type X there is a unique y of type Y to which it converts, then
>> we do not necessarily have transitivity(1). If the conversion is a
>> function but not total then we do not necessarily have
>> transitivity(2). If the conversion is not a function (or we allow more
>> than one conversion function), then we will have interesting
>> non-deterministic behaviour in our programs.
>>
>> (1) suppose we have a total functions fxy: X->Y, fyz Y->Z, fxw: X->W
>> and fwz: W->Z. fyz(fxy(x)) is not necessarily equal to fwz(fxw(x)) for
>> all x of type X, therefore there is no function fxz: X->Z which we can
>> choose as the conversion from X to Z such that it is equivalent to
>> conversions via both W and Y.
>
>This should cause a compilation error if neither of conversion paths
>was not made preferable (like C++ does in case 1+1.0). Languages are
>full of such or similar problems:

But this means that "there is an intentionally defined conversion to"
is either not transitive, or is ambiguous. Neither of these is a good
characteristic for a subtype relation.


>
>class A
>{
>public :
> void Foo ();
>};
>class B : public A {};
>class C : public A {};
>class D : public B, public C {};
>
>In full conformace with LSP (:-)),

I believe that this does not satisfy LSP.


>
>D X;
>
>X.Foo (); -- Compilation error
>
>> (2) If the functions are not required to be total then we have the
>> simpler possibility that for every x of type X, fyz is not defined for
>> fxy(x), so there is no z of type Z to which any x converts by this
>> route.
>
>This should cause a run-time error (in worst case). Example:
>
>int I = 1e37;

Another example of why "there is a converter" is not a subtype
relation.

>
>> Perhaps you could give me a reference to where "reference shift" is
>> defined, and where it is used in defining LSP?
>
>This is how it is implemented in C++, Ada 95 etc. and this is a direct
>consequence of the statement "if B is a subtype of A, then each instance
>of B contains an instance of A". Is it in your opinion a right way to
>implement LSP as you understand it?

As I said, I don't know about Ada, but C++ does not enforce LSP in the
subtype relation it generates by inheritance. Since C++ does not
implement LSP, how C++ implements its class hierarchy is not relevant
to the question of the definition of LSP.

>
>> >> If you merely want to know that you will not get "method not
>> >> understood" errors then you can get away with the weaker signature
>> >> conformance subtype relation, but you have to deal with any
>> >> behavioural anomalies that may arise.
>> >

>> >Surely sqrt is a method of Float, however anomalies still arise if
>> >the argument is negative. It is silly to hope that it is possible to
>> >have an operation defined for all instances of a type. If this cannot
>> >be achieved even for the base type, why should we require it from its
>> >subtypes?
>>
>> I would expect the behaviour of sqrt to be well defined when presented
>> with a negative argument; for example, in a language with exceptions I
>> would expect there to be some specified exception thrown in this case.
>> This is not anomalous behaviour.
>
>Similarily, not an anomalous behaviour is raising an exception in:
>
>X : Positive := Decrement (1);
>
>or
>
>X : Square;
>...
>Resize (X, 10.0, 20.0);

The anomalies arise when code that knows only the supertype gets
unexpected behaviour, like an exception, because it was given
something that was not a subtype in the LSP sense but was a subtype
within the language constraints. Until you have an example where a
subtype is used where the supertype is expected, you will not be in
the area that LSP addresses.

Would you expect an exception from this code (assuming that Resize is
defined for Rectangle with the appropriate signature):

X : Rectangle;


...
Resize (X, 10.0, 20.0);

If you have subtypes of Rectangle but not LSP, the only available
answer is "I don't know".

sk...@my-deja.com

unread,
Nov 14, 2000, 3:00:00 AM11/14/00
to
In article <8upsbl$4fd$1...@plutonium.compulink.co.uk>,
bran...@cix.compulink.co.uk wrote:

> In C++ we can override the resize routine to raise a runtime error
> manually. (We can do this in Eiffel too, of course.)

Indeed.
The problem however is the "expected behaviour" effect.
If rectangle never raises an exception (not defined in the interface
etc) but circle does, then a rectangle client can get an exception
raised that it was unaware it needed to trap.

That's why I'm interested in the Eiffel scheme it is indeed
supported in the runtime env etc.

dmitr...@my-deja.com

unread,
Nov 14, 2000, 3:00:00 AM11/14/00
to
In article <3a107d83...@news.tesco.net>,

owen...@waitrose.deletethis.com (Owen Rees) wrote:
> On Fri, 10 Nov 2000 13:24:50 GMT, dmitr...@my-deja.com wrote:
>
> >subtype Positive is Integer range 1..Integer'Last;
> >
> >It is Ada 83 and Ada is a very carefully designed language.
>
> I am afraid that although I know an unnecessarily large number of
> programming languages, Ada is not one of them. This makes it difficult
> for me to understand what point you are making. If you are saying that
> your definition of "subtype" is the Ada definition, then it would be
> easier to just say so.

Ada has many differently defined "subtypes". I hope that my definition
would cover all of them. As for Positive vs. Integer, we could find an
equivalent in C++: long vs. int. I would treat int as a subtype of long.

> >Existence of a conversion means that the programmer (explicitly or
> >implicitly) has defined one.
>
> I understand, and have used, explicitly defined conversions, but you
> will have to explain what you mean by an implicitly defined
> conversion.

For instance:

class B : public A {...};

implicitly defines two conversions B->A and B<-A, when B is the type of
an actual parameter of type A.

> >> and so I assume that there are some unstated assumptions that limit
> >> the kind of conversions you will allow.
> >
> >Yes, only the intentionally defined ones.
>
> Would I be right in restating your concept of subtype as "there is an
> intentionally defined conversion to" rather than "can be converted
> to"? This seems to be what you have just said.

You are right. I apologize, however in a good language nothing should
be made by default (:-))

> >This should cause a compilation error if neither of conversion paths
> >was not made preferable (like C++ does in case 1+1.0). Languages are
> >full of such or similar problems:
>
> But this means that "there is an intentionally defined conversion to"
> is either not transitive, or is ambiguous. Neither of these is a good
> characteristic for a subtype relation.

Ambiguity that can be detected at compile time is harmless, there is
still a possibility to resolve the conflict by implicit type conversion.

> >class A
> >{
> >public :
> > void Foo ();
> >};
> >class B : public A {};
> >class C : public A {};
> >class D : public B, public C {};
> >
> >In full conformace with LSP (:-)),
>
> I believe that this does not satisfy LSP.

Or satisfies it too much (twice), so that the compiler cannot decide
which one to choose? (:-))

> >int I = 1e37;
>
> Another example of why "there is a converter" is not a subtype
> relation.

Shall we make above illegal? And what about string types. In some
languages there are six different string types. Instances of that types
are convertible to each other, but not always. We could say this
violates LSP and force the programmer to place the conversion
explicitly, or say, down with LSP, and leave this to the compiler.
Semantically this would change nothing. The fact that not all
rectangles are squares, or that there are people which names are longer
than 80 characters and cannot be represented in ASCII character set, is
beyond our influence.

> As I said, I don't know about Ada, but C++ does not enforce LSP in the
> subtype relation it generates by inheritance. Since C++ does not
> implement LSP, how C++ implements its class hierarchy is not relevant
> to the question of the definition of LSP.

OK. How would you implement LSP-conform parameter passing?

> >Similarily, not an anomalous behaviour is raising an exception in:
> >
> >X : Positive := Decrement (1);
> >
> >or
> >
> >X : Square;
> >...
> >Resize (X, 10.0, 20.0);
>
> The anomalies arise when code that knows only the supertype gets
> unexpected behaviour, like an exception, because it was given
> something that was not a subtype in the LSP sense but was a subtype
> within the language constraints. Until you have an example where a
> subtype is used where the supertype is expected, you will not be in
> the area that LSP addresses.

Good point. But note, that no exception will occur in Decrement or
Resize. Therefore the methods of the supertype remain intact. The
problem arises when we try to write an illegal value back. We will
never encounter problems in methods of the supertype, only in class-
wide methods (for instance, by resizing all rectangles of a list, that
may possibly include squares). Enforcing LSP does not solve this
problem, it just makes impossible to write such methods. The best
medicine against scurf ... (:-))

Note also that dispatching methods also violate LSP in this sense. If
Resize of rectangles is virtual (dispatching) and someone override it,
then there is no way to warranty that this Resize would not raise some
inappropriate exception.

> Would you expect an exception from this code (assuming that Resize is
> defined for Rectangle with the appropriate signature):
>
> X : Rectangle;
> ...
> Resize (X, 10.0, 20.0);
>
> If you have subtypes of Rectangle but not LSP, the only available
> answer is "I don't know".

Surely.

BTW in a mission-critical application I would always expect an
exception.

More interesting is to have a list of all possible exceptions. Some
languages tried this. I am not sure whether it is worth the
restrictions such requrement would impose.

and do you know which exceptions would raise (:-))

X : File;
Y : Code;
...
Compile (X, Y);
Y.Execute;

This is the price we should pay for abilities

1. to extend and reuse code written by other people.
2. to have different representations of same things.
3. to mix things that not exactly fit to each other.

--
Regards,
Dmitry Kazakov

Owen Rees

unread,
Nov 15, 2000, 3:00:00 AM11/15/00
to
On Tue, 14 Nov 2000 14:28:06 GMT, dmitr...@my-deja.com wrote:

>Ada has many differently defined "subtypes". I hope that my definition
>would cover all of them. As for Positive vs. Integer, we could find an
>equivalent in C++: long vs. int. I would treat int as a subtype of long.

The issue as I see it is that "subtype" is used to mean different
things in different programming languages, and by different people.
This is why defining some other term such as "is a" in terms of
"subtype" does not really tell you anything; you have to say which of
the many definitions of subtype you mean.

As for subrange types, in the cases I have seen, the subrange type may
be called a subtype of the type from which it is taken, but if the
range constraints are enforced, the subrange is not an LSP-subtype of
the original type. Also, I have only come across subranges where they
apply to basic types in languages that do not try to incorporate
everything into their object model (if they have one) so their
relevance to the issues of objects and inheritance is doubtful.

>> I understand, and have used, explicitly defined conversions, but you
>> will have to explain what you mean by an implicitly defined
>> conversion.
>
>For instance:
>
>class B : public A {...};
>
>implicitly defines two conversions B->A and B<-A, when B is the type of
>an actual parameter of type A.

That looks like C++. The Annotated C++ Reference Manual does talk
about implicitly converting pointers to derived classes to pointers to
base classes, and the same for references, but it is clear that this
is describing the mechanism by which the same object may be seen as
being of different classes. This is completely different from the kind
of thing you can do with user defined conversions, and I think it is
unhelpful for the conventional implementation machinery to have been
exposed and described as a conversion.

>> But this means that "there is an intentionally defined conversion to"
>> is either not transitive, or is ambiguous. Neither of these is a good
>> characteristic for a subtype relation.
>
>Ambiguity that can be detected at compile time is harmless, there is
>still a possibility to resolve the conflict by implicit type conversion.

The problem is that ambiguities resolved implicitly through
intermediate types may be resolved differently in different places,
and this can lead to surprises.



>
>> >class A
>> >{
>> >public :
>> > void Foo ();
>> >};
>> >class B : public A {};
>> >class C : public A {};
>> >class D : public B, public C {};
>> >
>> >In full conformace with LSP (:-)),
>>
>> I believe that this does not satisfy LSP.
>
>Or satisfies it too much (twice), so that the compiler cannot decide
>which one to choose? (:-))

If I attempt to use a D as if it were an A, it does not behave as an
A, therefore it does not satisfy LSP. It does not matter how early I
discover this.

>
>> >int I = 1e37;
>>
>> Another example of why "there is a converter" is not a subtype
>> relation.
>
>Shall we make above illegal?

Automatic conversion between types has existed at least since FORTRAN
was invented, there is no reason for it to be made illegal. I have no
problem with "there is an automatic conversion", but that does not
cause the types to be related by subtyping. Depending on automatic
conversion may be a mistake. I once saw this in a FORTRAN (66)
program:
EPS = 1*10**(-6)
Those who know FORTRAN will realise that the programmer probably
intended "1E-6", but had written an expression that evaluates to zero.

Algol68 did not make "ref int" a subtype of "int", but there was a
dereferencing coercion that would be applied to a "ref int" value when
an "int" was required in an appropriate context. Similarly, (I don't
remember the syntax) a proc returning int would be deprocedured (i.e.
called!) in an appropriate context where an int was required, but this
did not cause the types to be related by subtyping.

C++ allows you to define a conversion function from a class to a basic
type, this does not cause the class and the basic type to be related
by subclassing or subtyping. You can write conversion functions
between arbitrary unrelated classes too.

> And what about string types. In some
>languages there are six different string types. Instances of that types
>are convertible to each other, but not always. We could say this
>violates LSP and force the programmer to place the conversion
>explicitly, or say, down with LSP, and leave this to the compiler.
>Semantically this would change nothing. The fact that not all
>rectangles are squares, or that there are people which names are longer
>than 80 characters and cannot be represented in ASCII character set, is
>beyond our influence.

Since the existence of a conversion does not imply subtyping, I don't
see how LSP is relevant.

>
>> As I said, I don't know about Ada, but C++ does not enforce LSP in the
>> subtype relation it generates by inheritance. Since C++ does not
>> implement LSP, how C++ implements its class hierarchy is not relevant
>> to the question of the definition of LSP.
>
>OK. How would you implement LSP-conform parameter passing?

I don't need to. If I choose to define a set of classes so that the
subclass relationship generates an LSP-conformant type relationship,
then I can just use the parameter passing mechanisms of existing
languages. They don't check that I have satisfied LSP, but they don't
get in the way if I have. I would go along with this quote taken from
the paper I mentioned in an earlier message: "We believe that
programmers will find our approaches relatively easy to apply and
expect them to be used primarily in an informal way."

Of course, the language may have its own subtyping rules that I have
to satisfy as well, and that may be inconvenient if it imposes
additional restrictions, but that has nothing to do with LSP.

>
>> >Similarily, not an anomalous behaviour is raising an exception in:
>> >
>> >X : Positive := Decrement (1);
>> >
>> >or
>> >
>> >X : Square;
>> >...
>> >Resize (X, 10.0, 20.0);
>>
>> The anomalies arise when code that knows only the supertype gets
>> unexpected behaviour, like an exception, because it was given
>> something that was not a subtype in the LSP sense but was a subtype
>> within the language constraints. Until you have an example where a
>> subtype is used where the supertype is expected, you will not be in
>> the area that LSP addresses.
>
>Good point. But note, that no exception will occur in Decrement or
>Resize. Therefore the methods of the supertype remain intact. The
>problem arises when we try to write an illegal value back.

I'm sorry, I thought we were discussing OO programming languages. I
thought that the problem was a resize method being invoked on an
object actually of class Square, but where the invoker sees it as of
class Rectangle. There is no "write back" in this case, the method
updates the state of the Square object so that it is not a square, or
does something else like throw an exception to avoid doing that.

> We will
>never encounter problems in methods of the supertype, only in class-
>wide methods (for instance, by resizing all rectangles of a list, that
>may possibly include squares). Enforcing LSP does not solve this
>problem, it just makes impossible to write such methods. The best
>medicine against scurf ... (:-))

I don't understand which class you mean when you write "class-wide
methods" if these are not methods of the supertype. I also don't see
how putting objects into a list is going to make any difference.

Since LSP is not enforced, at least not in any language I am aware of,
if something is stopping you from writing the methods you want it is
not LSP.

>
>Note also that dispatching methods also violate LSP in this sense. If
>Resize of rectangles is virtual (dispatching) and someone override it,
>then there is no way to warranty that this Resize would not raise some
>inappropriate exception.

Yes, LSP is not enforced, it is just a way of doing things so that you
can think about you program in parts rather than having to think about
every detail of all of it at once to know what it will do.

>
>> Would you expect an exception from this code (assuming that Resize is
>> defined for Rectangle with the appropriate signature):
>>
>> X : Rectangle;
>> ...
>> Resize (X, 10.0, 20.0);
>>
>> If you have subtypes of Rectangle but not LSP, the only available
>> answer is "I don't know".
>
>Surely.
>
>BTW in a mission-critical application I would always expect an
>exception.
>
>More interesting is to have a list of all possible exceptions. Some
>languages tried this. I am not sure whether it is worth the
>restrictions such requrement would impose.

Declaring all the exceptions that can be thrown by a method, and using
a type checker that enforces that, allow you to detect certain kinds
of error early. While it is good to handle things you have not
forseen, it is better to eliminate as many errors as possible as well.
It is not clear to me that there is any restriction beyond having to
write your code carefully.


>
>and do you know which exceptions would raise (:-))

It is not that hard to write a type inferencer that calculates the
exceptions that can be thrown from any block of code provided that the
language is defined so as to make this possible. This is easy enough
to check against exception specifications in operation signatures.
Getting past the type checker can be a challenge in that kind of
language, but my experience of that kind of system is that you
understand a lot more about your program when you do.

>
>X : File;
>Y : Code;
>...
>Compile (X, Y);
>Y.Execute;

This requires a dynamically extensible type system; I am more familiar
with the Lisp functions that do these kinds of things.

I am not sure what point you are making here. Type checking in this
case is rather harder than it is for the problem we started with, but
the existence of a hard problem is not a reason for refusing to try to
solve a simpler problem.

>
>This is the price we should pay for abilities
>
>1. to extend and reuse code written by other people.

and get obscure failures in parts of the system we have not changed
unless we do it at least in the spirit of LSP.

>2. to have different representations of same things.

and get obscure errors when we apply operations for one representation
to the other because we have not taken any care to think which we are
using.

>3. to mix things that not exactly fit to each other.

and since we can't be bothered to create a properly fitting adapter,
we get obscure errors where the pieces do not fit properly.

dmitr...@my-deja.com

unread,
Nov 15, 2000, 3:00:00 AM11/15/00
to
In article <3a124d40...@news.waitrose.com>,

owen...@waitrose.deletethis.com (Owen Rees) wrote:
> On Tue, 14 Nov 2000 14:28:06 GMT, dmitr...@my-deja.com wrote:
>
> The issue as I see it is that "subtype" is used to mean different
> things in different programming languages, and by different people.
> This is why defining some other term such as "is a" in terms of
> "subtype" does not really tell you anything; you have to say which of
> the many definitions of subtype you mean.

1. B is an in-subtype of A, if there is a conversion B->A
2. B is an out-subtype of A, if there is a conversion B<-A
3. B is an inout-subtype of A, if there are both conversions

> As for subrange types, in the cases I have seen, the subrange type may
> be called a subtype of the type from which it is taken, but if the
> range constraints are enforced, the subrange is not an LSP-subtype of
> the original type.

Yes, LSP fails here for the same reason as for Square/Rectangle.

> Also, I have only come across subranges where they
> apply to basic types in languages that do not try to incorporate
> everything into their object model (if they have one) so their
> relevance to the issues of objects and inheritance is doubtful.

There should be no difference between built-in and user-defined types.
Which means that built-in types could be used in inheritance. Then
imposing constraints is a very important mechanism. It is not only
ranges (which alone is more important than LSP-subtyping, IMO). It is
also more general discriminanted types. For instance, a string type is
usually implemented as an array parametrized by the array size.
Constrained subtypes of the string type are strings of some fixed
length.

> >For instance:
> >
> >class B : public A {...};
> >
> >implicitly defines two conversions B->A and B<-A, when B is the type
> >of an actual parameter of type A.
>
> That looks like C++. The Annotated C++ Reference Manual does talk
> about implicitly converting pointers to derived classes to pointers to
> base classes, and the same for references, but it is clear that this
> is describing the mechanism by which the same object may be seen as
> being of different classes.

Yes this is how it works, if you want to use B in place of A. Because
unlike mathematics, in a language an instance of B is NEVER of A, some
kind of conversion is required.

> This is completely different from the kind
> of thing you can do with user defined conversions, and I think it is
> unhelpful for the conventional implementation machinery to have been
> exposed and described as a conversion.

The compiler also generates conversions for int->long and int<-long.
How is it different from B->A and B<-A?

> >Ambiguity that can be detected at compile time is harmless, there is
> >still a possibility to resolve the conflict by implicit type
> >conversion.
>
> The problem is that ambiguities resolved implicitly through
> intermediate types may be resolved differently in different places,
> and this can lead to surprises.

Similarily to overloading and overriding resolution surprizes. In
general for any visibility rules, if you allow local scope declarations
without any limits, you'll have it.

> >> >class A
> >> >{
> >> >public :
> >> > void Foo ();
> >> >};
> >> >class B : public A {};
> >> >class C : public A {};
> >> >class D : public B, public C {};
> >> >
> >> >In full conformace with LSP (:-)),
> >>
> >> I believe that this does not satisfy LSP.
> >
> >Or satisfies it too much (twice), so that the compiler cannot decide
> >which one to choose? (:-))
>
> If I attempt to use a D as if it were an A, it does not behave as an
> A, therefore it does not satisfy LSP. It does not matter how early I
> discover this.

So if the compiler is broken and gives no error message, then above
will be LSP?

> >> >int I = 1e37;
> >>
> >> Another example of why "there is a converter" is not a subtype
> >> relation.
> >
> >Shall we make above illegal?
>
> Automatic conversion between types has existed at least since FORTRAN
> was invented, there is no reason for it to be made illegal. I have no
> problem with "there is an automatic conversion", but that does not
> cause the types to be related by subtyping.

[ LSP-subtyping! ]

> Depending on automatic
> conversion may be a mistake. I once saw this in a FORTRAN (66)
> program:
> EPS = 1*10**(-6)
> Those who know FORTRAN will realise that the programmer probably
> intended "1E-6", but had written an expression that evaluates to zero.

Yes, automatic conversions are bad practice. In PL/1 it is a nightmare.
But I do not want them automatic. I want to define my own ones where I
need it. Imagine a type hierarchy:

Image <--- GIF_Image
<--- JPEG_Image
...

X : GIF_Image;

Read (File, X);
X.Rotate (Pi / 2.0); -- Uses Image.Rotate
Write (File, X);

> Algol68 did not make "ref int" a subtype of "int", but there was a
> dereferencing coercion that would be applied to a "ref int" value when
> an "int" was required in an appropriate context. Similarly, (I don't
> remember the syntax) a proc returning int would be deprocedured (i.e.
> called!) in an appropriate context where an int was required, but this
> did not cause the types to be related by subtyping.
>
> C++ allows you to define a conversion function from a class to a basic
> type, this does not cause the class and the basic type to be related
> by subclassing or subtyping. You can write conversion functions
> between arbitrary unrelated classes too.

C++ limits the conversion path by one conversion. I want any number of
them. Further if a conversion exists then types become related, for I
can call methods of one type with arguments of another. You can refuse
to call it a subtyping relation. But there is no difference between
them.

> Since the existence of a conversion does not imply subtyping, I don't
> see how LSP is relevant.

If you define subtyping as LSP-subtyping, then there is no place for
discussion. Surely, how can a LSP-subtyping be non-LSP?

> >OK. How would you implement LSP-conform parameter passing?
>
> I don't need to. If I choose to define a set of classes so that the
> subclass relationship generates an LSP-conformant type relationship,
> then I can just use the parameter passing mechanisms of existing
> languages. They don't check that I have satisfied LSP, but they don't
> get in the way if I have. I would go along with this quote taken from
> the paper I mentioned in an earlier message: "We believe that
> programmers will find our approaches relatively easy to apply and
> expect them to be used primarily in an informal way."

Let's I am a language developer and I am asking for an advice,
because "we believe it is easy" is a bit too informal for language
semantic (:-))

> Of course, the language may have its own subtyping rules that I have
> to satisfy as well, and that may be inconvenient if it imposes
> additional restrictions, but that has nothing to do with LSP.

That's right

> >> >Similarily, not an anomalous behaviour is raising an exception in:
> >> >
> >> >X : Positive := Decrement (1);
> >> >
> >> >or
> >> >
> >> >X : Square;
> >> >...
> >> >Resize (X, 10.0, 20.0);
> >>
> >> The anomalies arise when code that knows only the supertype gets
> >> unexpected behaviour, like an exception, because it was given
> >> something that was not a subtype in the LSP sense but was a subtype
> >> within the language constraints. Until you have an example where a
> >> subtype is used where the supertype is expected, you will not be in
> >> the area that LSP addresses.
> >
> >Good point. But note, that no exception will occur in Decrement or
> >Resize. Therefore the methods of the supertype remain intact. The
> >problem arises when we try to write an illegal value back.
>
> I'm sorry, I thought we were discussing OO programming languages. I
> thought that the problem was a resize method being invoked on an
> object actually of class Square, but where the invoker sees it as of
> class Rectangle. There is no "write back" in this case, the method
> updates the state of the Square object so that it is not a square, or
> does something else like throw an exception to avoid doing that.

LSP-OOPL! This is exactly the point we started about week ago. It is an
attempt to pass Square by reference (= meaning of obscure "invoked on
Square"). Note that the exact meaning is:

1. Reference to Square is converted to a reference to a Rectangle.
2. The new reference shall point inside Square
3. The reference is passed to Resize

This is what I call LSP (in its language aspect) and this will never
ever work with:

1. Types that do not contain their bases
2. Methods that use by-copy parameter passing

> >We will
> >never encounter problems in methods of the supertype, only in class-
> >wide methods (for instance, by resizing all rectangles of a list,
> >that
> >may possibly include squares). Enforcing LSP does not solve this
> >problem, it just makes impossible to write such methods. The best
> >medicine against scurf ... (:-))
>
> I don't understand which class you mean when you write "class-wide
> methods" if these are not methods of the supertype. I also don't see
> how putting objects into a list is going to make any difference.

A class-wide method of type A is defined for all subtypes of A. The
difference to a normal method is that no dispatch happens. The actual
parameter is passed as-is (no type conversion). Dispatch usually
happens within the method. C++ does not have this feature because it
makes no difference between values of a specific type and values of
the "class rooted in a type" (Ada term for closure of subtypes).

> Since LSP is not enforced, at least not in any language I am aware of,
> if something is stopping you from writing the methods you want it is
> not LSP.
>
> >Note also that dispatching methods also violate LSP in this sense. If
> >Resize of rectangles is virtual (dispatching) and someone override
> >it, then there is no way to warranty that this Resize would not
> >raise some inappropriate exception.
>
> Yes, LSP is not enforced, it is just a way of doing things so that you
> can think about you program in parts rather than having to think about
> every detail of all of it at once to know what it will do.

Shall LSP be enforced?
How would you call the relation between int and long?

> >More interesting is to have a list of all possible exceptions. Some
> >languages tried this. I am not sure whether it is worth the
> >restrictions such requrement would impose.
>
> Declaring all the exceptions that can be thrown by a method, and using
> a type checker that enforces that, allow you to detect certain kinds
> of error early. While it is good to handle things you have not
> forseen, it is better to eliminate as many errors as possible as well.
> It is not clear to me that there is any restriction beyond having to
> write your code carefully.

What about exception Task_Cancelled_By_Stupid_Operator, dynamically
linked code, dispatching methods.

I believe the issue was discussed during developing of Ada 95 standard.
It was dropped.

> >and do you know which exceptions would raise (:-))
>
> It is not that hard to write a type inferencer that calculates the
> exceptions that can be thrown from any block of code provided that the
> language is defined so as to make this possible. This is easy enough
> to check against exception specifications in operation signatures.
> Getting past the type checker can be a challenge in that kind of
> language, but my experience of that kind of system is that you
> understand a lot more about your program when you do.

I am not against. I simply do not know how (:-()

> >X : File;
> >Y : Code;
> >...
> >Compile (X, Y);
> >Y.Execute;
>
> This requires a dynamically extensible type system; I am more familiar
> with the Lisp functions that do these kinds of things.

Any statically typed language could face dynamically extensible types.
If you have a library unit which implement some thing. Obviously this
implementation should be free to PRIVATELY extend some types. No
checker would be aware of this extension till unit elaboration, or even
later. A carefully designed language should also make such extensions
invisible out of scope and reversible upon unit epilogue.

> >This is the price we should pay for abilities
> >
> >1. to extend and reuse code written by other people.
>
> and get obscure failures in parts of the system we have not changed
> unless we do it at least in the spirit of LSP.

LSP won't prevent it.

> >2. to have different representations of same things.
>
> and get obscure errors when we apply operations for one representation
> to the other because we have not taken any care to think which we are
> using.

Never, if representations are isomorphic and sometimes if they are not.
Surely, a programmer should know what he does. Errors in type hierarchy
design are worst ones.

> >3. to mix things that not exactly fit to each other.
>
> and since we can't be bothered to create a properly fitting adapter,
> we get obscure errors where the pieces do not fit properly.

If a properly fitting adapter can be built, let's make it the type
conversion. Unfortunately, in some cases there is no one.

sk...@my-deja.com

unread,
Nov 17, 2000, 3:00:00 AM11/17/00
to
In article <3a124d40...@news.waitrose.com>,
owen...@waitrose.deletethis.com (Owen Rees) wrote:

>As for subrange types, in the cases I have seen, the subrange type may
>be called a subtype of the type from which it is taken, but if the
>range constraints are enforced, the subrange is not an LSP-subtype of
>the original type. Also, I have only come across subranges where they
>apply to basic types in languages that do not try to incorporate
>everything into their object model (if they have one) so their
>relevance to the issues of objects and inheritance is doubtful.

I = integer
BI = integer[1..10]

class I
{
assign(I) ;
// ...
}

class BoundedI<int L, int U> : public I
{
assign(I) ;
// ...
}

typedef BoundedI<1,10> BI ;


The relevance is not doubtful now (FWIW, I chose the I/BI types from a
paper on this very subject) . :-)

You are correct though about subranges wrt the LSP.
BI is not substitutable for I because it is not completely compatible
with I. The mutating ops (assign etc) are the offenders.

However, BI is read-only substitutable with I (I can compare their
values etc) . This is a weaker form of the LSP, but LSP-conformant
nonetheless. And useful too.


Regards,
Steven Perryman

dmitr...@my-deja.com

unread,
Nov 17, 2000, 3:00:00 AM11/17/00
to
In article <8v2usa$4ov$1...@nnrp1.deja.com>,

sk...@my-deja.com wrote:
> You are correct though about subranges wrt the LSP.
> BI is not substitutable for I because it is not completely compatible
> with I. The mutating ops (assign etc) are the offenders.
>
> However, BI is read-only substitutable with I (I can compare their
> values etc) . This is a weaker form of the LSP, but LSP-conformant
> nonetheless. And useful too.

This is why I like to split subtyping into in- and out-subtyping. In-
subtype inherits only in-methods. As an in-subtype, BI will always be
substitutable. As a full inout-subtype it will have obvious problems.

Very often only in-subtyping is reguired. Literals can be considered as
in-subtypes of the corresponding types.

Similarily, if EI is I with an extended range, it will be a perfect out-
subtype and a problematic in-subtype. [ Such thing does not exist in
languages I know, but it will be useful for instance, when some extra
value should be added to an enumeration type (such as escape character
etc.) ]

Owen Rees

unread,
Nov 17, 2000, 8:18:33 PM11/17/00
to
On Wed, 15 Nov 2000 14:57:55 GMT, dmitr...@my-deja.com wrote:

>Yes this is how it works, if you want to use B in place of A. Because
>unlike mathematics, in a language an instance of B is NEVER of A, some
>kind of conversion is required.

In languages that do not declare the types of variables or parameters,
there are no conversions. I believe that this is true of Smalltalk,
and I know that it is true of LOOPS and CLOS. Methods written to use
some class will work with subclasses of that class even when the
subclass is written later.

For an example where types are declared and checked, I refer you to
"The Java Language Specification" by Gosling, Joy and Steele ISBN
0-201-63451-1, First printing , August 1996, page 59, the penultimate
paragraph of the section on "Widening Reference Conversions": "Such
conversions never require a special action at run time and therefore
never throw an exception at run time. They consist simply in regarding
a reference as having some other type in a manner that can be proved
correct at compile time." I think it is unfortunate that they have
called these conversions since, unlike some other conversions, there
is no change to the run-time representation.

Despite the statement about simply regarding a reference as having
some other type, some people may believe that there is really a change
of representation in the implementation. We therefore consult "The
Java Virtual Machine Specification" by Lindholm and Yellin ISBN
0-201-63452-X, First Printing, September 1996, page 258, the
definition of the 'invokeinterface' operator, since that is where the
interesting action occurs. "The method table is searched for a method
whose name and descriptor are identical to the name and descriptor of
the resolved constant pool entry". Note that it says 'searched' which
is exactly how you implement the ability to use a subtype in place of
the type known at the invocation point, without having to convert the
reference from a subtype view to the expected type view.

If you want another example of the use of object references of
subtypes in places where a reference of the supertype is expected,
with no conversion, I refer you to CORBA. This is not a language, but
the object model, the IDL, and the type system do have relevant
properties. One of the advantages of studying distributed object
computing is that you cannot appeal to hidden mechanisms in the
infrastructure; you have to say what goes on the wire, and it has to
be complete enough for invocations to be fully specified. It also
makes you consider the case where a subtype is designed, created,
deployed and integrated into a running system where other elements
know only its supertype, and then those components are upgraded to
ones that do use the subtype. One reason why this kind of upgrade can
be done is that CORBA object references can be used directly where
supertype references are expected, and no conversions are needed.

I prefer to reserve the word "subtype" for the cases where entities
are actually of more than one type, and where knowing that B is a
subtype of A means that I can use something of type B directly
wherever something of type A is expected, without certain specified
bad things happening.

Owen Rees

unread,
Nov 17, 2000, 8:18:39 PM11/17/00
to
On Fri, 17 Nov 2000 09:47:55 GMT, sk...@my-deja.com wrote:

>In article <3a124d40...@news.waitrose.com>,


> owen...@waitrose.deletethis.com (Owen Rees) wrote:
>
>>As for subrange types, in the cases I have seen, the subrange type may
>>be called a subtype of the type from which it is taken, but if the
>>range constraints are enforced, the subrange is not an LSP-subtype of
>>the original type. Also, I have only come across subranges where they
>>apply to basic types in languages that do not try to incorporate
>>everything into their object model (if they have one) so their
>>relevance to the issues of objects and inheritance is doubtful.
>

>I = integer
>BI = integer[1..10]
>
>class I
>{
> assign(I) ;
> // ...
>}
>
>class BoundedI<int L, int U> : public I
>{
> assign(I) ;
> // ...
>}
>
>typedef BoundedI<1,10> BI ;
>
>
>The relevance is not doubtful now (FWIW, I chose the I/BI types from a
>paper on this very subject) . :-)

I was thinking not so much of a container/variable for a subrange, but
rather of the subrange itself as a type, with an appropriate
definition of arithmetic. Although I believe that subranges as usually
seen are not interesting, they become interesting in an "everything is
an object" model.

If we try to make integer values objects, then introducing subranges
means we have to distinguish between "the 2 that is an 'int'" and "the
2 that is a [1..10]" since their 'plus' operations have to behave
differently if the subrange means anything. Speaking of the 'plus'
operation, what signature do we give it? What type do we return when
we add two subrange values?

>
>You are correct though about subranges wrt the LSP.
>BI is not substitutable for I because it is not completely compatible
>with I. The mutating ops (assign etc) are the offenders.

Mutating ops certainly cause problems, but you can have problems
without mutating ops. Suppose we create objects that represent values:

class I
{
bool equal(I i);
I plus(I i); // returns an object representing the value this+i
// ...
}

class BoundedI<int L, int U> : public I
{

I plus (I i); // returns (this-L+i)%(U-L)+L silently wrapping
// ...
}

typedef BoundedI<1,10> BI ;

BI b(6); // the BI that represents 6
I i(6);
I i2 = b; // use subclass as class
// ... somewhere later, inside some other method
i2.plus(i).equal(i.plus(i2)); // is false

Here, by passing a subrange value object, we have made it matter which
way round a programmer who knows nothing of BI, and wrote the code
before BI existed, has to add two instances of type I, and there is no
right answer because we could have passed things the other way round.

>
>However, BI is read-only substitutable with I (I can compare their
>values etc) . This is a weaker form of the LSP, but LSP-conformant
>nonetheless. And useful too.

I think my example demonstrates LSP violation with no mutable things
anywhere, and also shows how this can have effects that propagate into
obscure places with surprising effects.

dmitr...@my-deja.com

unread,
Nov 18, 2000, 3:00:00 AM11/18/00
to
In article <3a15a0b...@news.waitrose.com>,

owen...@waitrose.deletethis.com (Owen Rees) wrote:
> On Wed, 15 Nov 2000 14:57:55 GMT, dmitr...@my-deja.com wrote:
>
> >Yes this is how it works, if you want to use B in place of A. Because
> >unlike mathematics, in a language an instance of B is NEVER of A,
> >some kind of conversion is required.
>
> In languages that do not declare the types of variables or parameters,
> there are no conversions. I believe that this is true of Smalltalk,
> and I know that it is true of LOOPS and CLOS. Methods written to use
> some class will work with subclasses of that class even when the
> subclass is written later.

Undeclared or anonymous types are still types. There is no such thing
as a typeless value. There also could be languages where all variables
have same type. IMO, they are of little interest.

Even if the conversion does nothing in some rare cases (namely if B has
the representation of A) it is still a conversion. Otherwise, either B
is A, or the language is not typed.

Distributed computing is a good example of a situation where by-
reference parameter passing is impossible. How would a LSP
system "invoke" a remote method on the local object? How can you ensure
that the network will behave "as expected", and your carefully designed
method would not raise something
like "Server_not_responding_still_trying"?

dmitr...@my-deja.com

unread,
Nov 18, 2000, 3:00:00 AM11/18/00
to
In article <3a15ccae...@news.waitrose.com>,
owen...@waitrose.deletethis.com (Owen Rees) wrote:

> If we try to make integer values objects, then introducing subranges
> means we have to distinguish between "the 2 that is an 'int'" and "the
> 2 that is a [1..10]" since their 'plus' operations have to behave
> differently if the subrange means anything. Speaking of the 'plus'
> operation, what signature do we give it? What type do we return when
> we add two subrange values?

There is no need to distinguish 2 and 2. We just make all integer
literals of type UNIVERSAL_INTEGER. We define inheritable operations on
that type:

function "+" (Left, Right : UNIVERSAL_INTEGER) return UNIVERSAL_INTEGER;
procedure ":=" (Left : out UNIVERSAL_INTEGER; Right :
UNIVERSAL_INTEGER);

Then the type I is simply an inout-subtype of UNIVERSAL_INTEGER, which
means that two conversions are defined: UNIVERSAL_INTEGER->I and
UNIVERSAL_INTEGER<-I. When we write 2 + 2, the result is 4 of
UNIVERSAL_INTEGER. When we write

X : I;
...
X := X + 2;

It means:

1. Call to "+" (Left, Right : UNIVERSAL_INTEGER) return
UNIVERSAL_INTEGER;
1a. Convert X using I->UNIVERSAL_INTEGER
1b. Call "+" (the result is of UNIVERSAL_INTEGER)
2. Call to ":=" (Left : out UNIVERSAL_INTEGER, Right :
UNIVERSAL_INTEGER);
2a. Construct temp UNIVERSAL_INTEGER
2b. Call ":=" on temp and the reult of 1.
2c. Store temp to X unsing I<-UNIVERSAL_INTEGER

[ 2a. + 2c. is the procedure of passing an actual parameter of some out-
subtype to the formal parameter of the base type in out-mode. 1a. is
how it is accomplished in in-mode. For inout-mode you take 1a. + 2c. ]

Similarily to this, BI is merely derived from I. BI inherits "+"
and ":=" from UNIVERSAL_INTEGER via I. The conversion BI<-I raises
Constraint_Error if the argument is out of the range of BI.

> >You are correct though about subranges wrt the LSP.
> >BI is not substitutable for I because it is not completely compatible
> >with I. The mutating ops (assign etc) are the offenders.
>
> Mutating ops certainly cause problems, but you can have problems
> without mutating ops. Suppose we create objects that represent values:
>
> class I
> {
> bool equal(I i);
> I plus(I i); // returns an object representing the value this+i
> // ...
> }
>
> class BoundedI<int L, int U> : public I
> {
> I plus (I i); // returns (this-L+i)%(U-L)+L silently wrapping
> // ...
> }
>
> typedef BoundedI<1,10> BI ;
>
> BI b(6); // the BI that represents 6
> I i(6);
> I i2 = b; // use subclass as class
> // ... somewhere later, inside some other method
> i2.plus(i).equal(i.plus(i2)); // is false

Why so?

i2.plus(i).equal(i.plus(i2));

is

I::equal (I::plus (i2, i), I::plus (i, i2));

Though you could possible meant

I'Class i2 = b; // Class-wide object, that has type tag of BI

In this case i2.plus(i) and i.plus(i2) would dispatch (if plus is
dispatching) to different targets, as you correctly pointed. But this
was the intent (or error) of the developer, who overrode I::plus only
partially. In case of double dispatch he should override I::plus triple
for signatures (BI, I), (I, BI) and (BI, BI). [ If the result is also
dispatching, it would be 7 different signatures (:-)) ]

Though I see no big reason in dispatching plus for numerics. If plus is
not dispatching, then i2.plus(i) should give type error: i2 is neither
I nor BI (it is of I'Class).

> Here, by passing a subrange value object, we have made it matter which
> way round a programmer who knows nothing of BI, and wrote the code
> before BI existed, has to add two instances of type I, and there is no
> right answer because we could have passed things the other way round.

This is the question of the language design, if we want to have
multiple dispatch. It has nothing to do with LSP vs. non-LSP subtyping.
In any case such errors are statically detectable.

> >However, BI is read-only substitutable with I (I can compare their
> >values etc) . This is a weaker form of the LSP, but LSP-conformant
> >nonetheless. And useful too.
>
> I think my example demonstrates LSP violation with no mutable things
> anywhere, and also shows how this can have effects that propagate into
> obscure places with surprising effects.

Yes, overriding may have strange effects. Multiple dispatch could be
extremely dangerous, if not somehow limited (I do not know, how)

Owen Rees

unread,
Nov 18, 2000, 9:14:23 PM11/18/00
to
On Sat, 18 Nov 2000 12:13:09 GMT, dmitr...@my-deja.com wrote:

>In article <3a15a0b...@news.waitrose.com>,


> owen...@waitrose.deletethis.com (Owen Rees) wrote:
>> On Wed, 15 Nov 2000 14:57:55 GMT, dmitr...@my-deja.com wrote:
>>
>> >Yes this is how it works, if you want to use B in place of A. Because
>> >unlike mathematics, in a language an instance of B is NEVER of A,
>> >some kind of conversion is required.
>>
>> In languages that do not declare the types of variables or parameters,
>> there are no conversions. I believe that this is true of Smalltalk,
>> and I know that it is true of LOOPS and CLOS. Methods written to use
>> some class will work with subclasses of that class even when the
>> subclass is written later.
>

>Undeclared or anonymous types are still types. There is no such thing
>as a typeless value. There also could be languages where all variables
>have same type. IMO, they are of little interest.

Note that I said "languages that do not declare the types of variables
or parameters", not "languages that do not have types" or "languages
in which values do not have types". Consider this example, taken from
"Common Lisp The Language" (1984 edition):

(defun foo (x y) (* x (+ y 1 )))

Note that the types of the parameters are not declared although Common
Lisp has a comprehensive and well defined type system. Lisp may be of
little interest to you, but I suggest that a study of the various
dialects of Lisp will be enlightening to anyone who wishes to
understand programming languages, rather than merely use them. In
particular, students of OOP should study LOOPS and CLOS, both built on
Lisp, but being very different kinds of OOPL.

Perhaps someone who knows Smalltalk better than I do could comment,
but I believe that it too does not require type declarations of
variables or parameters. I find it hard to believe that anyone can
consider Smalltalk to be of no interest when studying OOP.

>> I prefer to reserve the word "subtype" for the cases where entities
>> are actually of more than one type, and where knowing that B is a
>> subtype of A means that I can use something of type B directly
>> wherever something of type A is expected, without certain specified
>> bad things happening.
>

>Even if the conversion does nothing in some rare cases (namely if B has
>the representation of A) it is still a conversion. Otherwise, either B
>is A, or the language is not typed.

I suggest you study Java and the JVM specification. You will then see
that you have not understood the mechanisms that lead to the
possibility of "do nothing" 'conversions'. The case occurs whenever
you use a variable or parameter that is declared to be of an interface
type. Interface types exist to allow otherwise unrelated classes to be
used as if they were of that type, so their representations will have
nothing in common except by coincidence. The case you consider rare is
in fact common in the use of that language. I have also been led to
believe that Java is quite popular, so I cannot see the use being rare
in terms of how many programmers make use of it either.

>
>Distributed computing is a good example of a situation where by-
>reference parameter passing is impossible.

I am very well aware of that, but I do not see it as a problem since
pass by reference (in the sense used in compiler and programming
language design) is uncommon in programming languages. The
conventional OOP parameter passing is better described as
"object-reference-by-value", and this is the conventional way to
implement object-based distributed systems too.

> How would a LSP
>system "invoke" a remote method on the local object? How can you ensure
>that the network will behave "as expected", and your carefully designed
>method would not raise something
>like "Server_not_responding_still_trying"?

Since LSP is a design principle that wise people choose to use in
order to avoid unexpected changes of behaviour appearing throughout a
system when some parts are updated, I cannot make any sense of the
phrase "an LSP system". I am not aware of any languages or systems
that enforce LSP if that is what you mean.

If you take the trouble to study the work that has been done on
distributed systems, you will find that the behaviour variations due
to faults in the infrastructure are taken into account in the
definitions of the type systems and interaction models. The
possibility of such faults is no reason to introduce the possibilty of
systemic design faults due to a lack of understanding of LSP or its
value. If you study the work that has been done on fault tolerance,
you will find that systemic design faults cannot be dealt with in any
easy way by fault tolerance mechanisms, but the infrastructure
failures usually can.

Owen Rees

unread,
Nov 18, 2000, 9:14:28 PM11/18/00
to
On Sat, 18 Nov 2000 14:29:59 GMT, dmitr...@my-deja.com wrote:

>In article <3a15ccae...@news.waitrose.com>,


> owen...@waitrose.deletethis.com (Owen Rees) wrote:
>
>> If we try to make integer values objects, then introducing subranges
>> means we have to distinguish between "the 2 that is an 'int'" and "the
>> 2 that is a [1..10]" since their 'plus' operations have to behave
>> differently if the subrange means anything. Speaking of the 'plus'
>> operation, what signature do we give it? What type do we return when
>> we add two subrange values?
>

>There is no need to distinguish 2 and 2. We just make all integer
>literals of type UNIVERSAL_INTEGER.

You seem to have completely missed the point of the example. The real
point is that people usually fail to distinguish between entities,
variables that can contain entities, and forms that evaluate to some
entity, when discussing types. I was attempting to undo some of the
confusion that was added by the introduction of subranges to the
discussion, by trying to separate the idea of a variable declared to
be able to hold only a subrange of integers, and an integer subrange
type.

>In this case i2.plus(i) and i.plus(i2) would dispatch (if plus is
>dispatching) to different targets, as you correctly pointed. But this
>was the intent (or error) of the developer, who overrode I::plus only
>partially. In case of double dispatch he should override I::plus triple
>for signatures (BI, I), (I, BI) and (BI, BI). [ If the result is also
>dispatching, it would be 7 different signatures (:-)) ]

You will have to explain this again. To get you started, here is a
complete example (in C++) that exhibits the behaviour (and yes it
leaks storage, but I don't care in this example):

#include <iostream.h>

class I
{
public:
int value;
I(int ival) : value(ival) { };
bool equal(I *i) { return value == i->value; };
virtual I* plus(I *i) { return new I(value+i->value); };
};

class useI
{
public:
void test(I *i1, I *i2);
};

void useI::test(I *i1, I *i2)
{
cout << i1->value << "+" << i2->value << "=" <<
i1->plus(i2)->value << endl;
cout << i2->value << "+" << i1->value << "=" <<
i2->plus(i1)->value << endl;
};

class BI : virtual public I
{
public:
BI(int ival) : I((ival-1)%10+1) { };
virtual I* plus(I *i) { return new
I(((value+i->value)-1)%10+1); };
};

int main(int argc, char* argv[])
{
I i(6);
BI b(6);
useI u;

u.test(&i,&b);

return 0;
}

>
>Though I see no big reason in dispatching plus for numerics. If plus is
>not dispatching, then i2.plus(i) should give type error: i2 is neither
>I nor BI (it is of I'Class).

Being numeric is not the point. The point is that I can create
surprising behaviour in such a small example, and do so with no
mutator operations.

>
>> Here, by passing a subrange value object, we have made it matter which
>> way round a programmer who knows nothing of BI, and wrote the code
>> before BI existed, has to add two instances of type I, and there is no
>> right answer because we could have passed things the other way round.
>

>This is the question of the language design, if we want to have
>multiple dispatch. It has nothing to do with LSP vs. non-LSP subtyping.
>In any case such errors are statically detectable.

I don't see what point you are making about language design, nor the
relevance of multiple dispatch. Could you explain this with reference
to my example please.

>
>> >However, BI is read-only substitutable with I (I can compare their
>> >values etc) . This is a weaker form of the LSP, but LSP-conformant
>> >nonetheless. And useful too.
>>
>> I think my example demonstrates LSP violation with no mutable things
>> anywhere, and also shows how this can have effects that propagate into
>> obscure places with surprising effects.
>

>Yes, overriding may have strange effects. Multiple dispatch could be
>extremely dangerous, if not somehow limited (I do not know, how)

I created a type that was a subtype within the rules of the language
(it let me use it in place of the supertype), but which was not an LSP
subtype. What mechanism I used to do this is beside the point. LSP
tells us that what I did is bad because it can surprise someone who
has no knowledge of my type, and who ought to need no knowledge of my
type. LSP does not tell me which mechanisms I should or should not
use, it just tells me what I should or should not do with them.

dmitr...@my-deja.com

unread,
Nov 19, 2000, 3:00:00 AM11/19/00
to
In article <3a172f12...@news.waitrose.com>,

owen...@waitrose.deletethis.com (Owen Rees) wrote:
> On Sat, 18 Nov 2000 14:29:59 GMT, dmitr...@my-deja.com wrote:
>
> >In article <3a15ccae...@news.waitrose.com>,
> > owen...@waitrose.deletethis.com (Owen Rees) wrote:
> >
> >> If we try to make integer values objects, then introducing
> >> subranges means we have to distinguish between "the 2 that is
> >> an 'int'" and "the
> >> 2 that is a [1..10]" since their 'plus' operations have to behave
> >> differently if the subrange means anything. Speaking of the 'plus'
> >> operation, what signature do we give it? What type do we return
> >> when we add two subrange values?
> >
> >There is no need to distinguish 2 and 2. We just make all integer
> >literals of type UNIVERSAL_INTEGER.
>
> You seem to have completely missed the point of the example. The real
> point is that people usually fail to distinguish between entities,
> variables that can contain entities, and forms that evaluate to some
> entity, when discussing types. I was attempting to undo some of the
> confusion that was added by the introduction of subranges to the
> discussion, by trying to separate the idea of a variable declared to
> be able to hold only a subrange of integers, and an integer subrange
> type.

I see no difference. "A variable declared to be able to hold only a
subrange of integers" shall have some type. The ability "to hold only a
subrange of integers" is a property of that type, which differentiates
it from the integer type.

> >In this case i2.plus(i) and i.plus(i2) would dispatch (if plus is
> >dispatching) to different targets, as you correctly pointed. But this
> >was the intent (or error) of the developer, who overrode I::plus only
> >partially. In case of double dispatch he should override I::plus
> >triple for signatures (BI, I), (I, BI) and (BI, BI). [ If the result
> >is also dispatching, it would be 7 different signatures (:-)) ]
>
> You will have to explain this again. To get you started, here is a
> complete example (in C++) that exhibits the behaviour (and yes it
> leaks storage, but I don't care in this example):
>
> #include <iostream.h>
>
> class I
> {
> public:
> int value;
> I(int ival) : value(ival) { };
> bool equal(I *i) { return value == i->value; };
> virtual I* plus(I *i) { return new I(value+i->value); };

--------^^^^^^^
I::plus is dispatching on the first (hidden) parameter

> };
>
> class useI
> {
> public:
> void test(I *i1, I *i2);
> };
>
> void useI::test(I *i1, I *i2)
> {
> cout << i1->value << "+" << i2->value << "=" <<
> i1->plus(i2)->value << endl;
> cout << i2->value << "+" << i1->value << "=" <<
> i2->plus(i1)->value << endl;
> };
>
> class BI : virtual public I
> {
> public:
> BI(int ival) : I((ival-1)%10+1) { };
> virtual I* plus(I *i) { return new
> I(((value+i->value)-1)%10+1); };
> };

BI::plus overrides I::plus

> int main(int argc, char* argv[])
> {
> I i(6);
> BI b(6);
> useI u;
>
> u.test(&i,&b);
>
> return 0;
> }
> >
> >Though I see no big reason in dispatching plus for numerics. If plus
> >is not dispatching, then i2.plus(i) should give type error: i2 is
> >neither I nor BI (it is of I'Class).
>
> Being numeric is not the point. The point is that I can create
> surprising behaviour in such a small example, and do so with no
> mutator operations.

The surprising behaviour is achieved due to multiple errors in the type
design:

1. The result of plus is returned via address of a newly created
object. Why?
2. The second argument is passed via address. Why?
3. I::plus is dispatching on the first argument. Why?
4. I::plus is not dispatching on the second argument. Why?
5. BI:plus overrides I::plus. To make it a modular integer? But
subranges have no sense for modular arithmetic. Also, why?

All "whys" have only one goal, that's achieving suprises. Your example
proves the well known saying that a real FORTRAN program can be written
in any language (:-))

> >> Here, by passing a subrange value object, we have made it matter
> >> which way round a programmer who knows nothing of BI, and wrote
> >> the code before BI existed, has to add two instances of type I,
> >> and there is no right answer because we could have passed things
> >> the other way round.
> >
> >This is the question of the language design, if we want to have
> >multiple dispatch. It has nothing to do with LSP vs. non-LSP
> >subtyping.
> >In any case such errors are statically detectable.
>
> I don't see what point you are making about language design, nor the
> relevance of multiple dispatch. Could you explain this with reference
> to my example please.

Your example functions because:

1. Plus is made dispatching. You should explain the reason why it have
to be one.
2. Even if Plus MUST be dispatching, then being a binary function
implementing a SYMMETRICAL operation, it should dispatch on both
arguments. You made it dispatching on only the first argument. Arguing
that C++ has only single dispatch leads us to the conclusion about an
insufficient language design.
3. If a method is dispatching on several arguments, and a subtype
overrides it, then all signatures involving the new type should be
overriden to avoid "surprises". That is what your example actually
shows. In a well designed language the compiler should warn the
programmer in such cases.

> >Yes, overriding may have strange effects. Multiple dispatch could be
> >extremely dangerous, if not somehow limited (I do not know, how)
>
> I created a type that was a subtype within the rules of the language
> (it let me use it in place of the supertype), but which was not an LSP
> subtype. What mechanism I used to do this is beside the point. LSP
> tells us that what I did is bad because it can surprise someone who
> has no knowledge of my type, and who ought to need no knowledge of my
> type. LSP does not tell me which mechanisms I should or should not
> use, it just tells me what I should or should not do with them.

In other words we should choose between LSP and dispatching methods.
Right?

BTW. I do not see how your example violates LSP. It seems that in your
opinion any overriding violates it:

class A
{
virtual void Foo (); // Makes something extremely useful
};

class B
{
virtual void Foo () { SystemShutdown (); }
};

Does this violate LSP?

If LSP is so worth, then let's enforce it. Let's give it a precise
definition in terms of a programming language, and then trow out of the
language all the constructs violating LSP. The question is how much of
the given language would remain?

Owen Rees

unread,
Nov 19, 2000, 9:18:26 PM11/19/00
to
On Sun, 19 Nov 2000 13:40:25 GMT, dmitr...@my-deja.com wrote:

>> You seem to have completely missed the point of the example. The real
>> point is that people usually fail to distinguish between entities,
>> variables that can contain entities, and forms that evaluate to some
>> entity, when discussing types. I was attempting to undo some of the
>> confusion that was added by the introduction of subranges to the
>> discussion, by trying to separate the idea of a variable declared to
>> be able to hold only a subrange of integers, and an integer subrange
>> type.
>
>I see no difference. "A variable declared to be able to hold only a
>subrange of integers" shall have some type. The ability "to hold only a
>subrange of integers" is a property of that type, which differentiates
>it from the integer type.

You still do not seem to have grasped the idea of a type that is the
set of integers between 1 and 10 inclusive, with a defined set of
operations over that set, without any reference to the variables we
might need later in order to hold values of that type. This is
elementary stuff when you study abstract algebra; you consider whether
what you have is a group, a ring, an integral domain, a field etc. If
you choose to bring subranges into the discussion, you should expect
those who have studied some mathematics to ask just what kind of
algebraic structure you are writing about. It turns out that you were
not writing about a subrange of integers type, but merely about
variables that reject values outside a certain range.

>
>> >In this case i2.plus(i) and i.plus(i2) would dispatch (if plus is
>> >dispatching) to different targets, as you correctly pointed. But this
>> >was the intent (or error) of the developer, who overrode I::plus only
>> >partially. In case of double dispatch he should override I::plus
>> >triple for signatures (BI, I), (I, BI) and (BI, BI). [ If the result
>> >is also dispatching, it would be 7 different signatures (:-)) ]
>>
>> You will have to explain this again. To get you started, here is a
>> complete example (in C++) that exhibits the behaviour (and yes it
>> leaks storage, but I don't care in this example):
>>
>> #include <iostream.h>
>>
>> class I
>> {
>> public:
>> int value;
>> I(int ival) : value(ival) { };
>> bool equal(I *i) { return value == i->value; };
>> virtual I* plus(I *i) { return new I(value+i->value); };
>--------^^^^^^^
>I::plus is dispatching on the first (hidden) parameter

This comment makes me think that perhaps you prefer the generic
function model of objects, rather than the so-called classical object
model. This is exactly why I have suggested elsewhere that it is
useful to study LOOPS and CLOS - LOOPS uses the classical model, but
CLOS uses the generic function model, and since both are based on
Lisp, it makes the comparison somewhat easier. Note that by defining
methods within classes, C++ tends to the classical view, but it does
have so much extra baggage that you can probably use it in a more
generic function style too.

You end up with a very different world view if you think of
invocations as being directed to a function that dispatches on the
type of its arguments (generic function), rather than thinking of
invocations being directed to object instances that dispatch on the
name of the method (classical).

The classical vs. generic function debate is probably as old as the
square/circle as a subtype (or not) of rectangle/ellipse.

If your world view is generic function based then I can see how a
behavioural notion of subtyping would seem strange, but it is very
natural in the classical model.

Not errors, choices to illustrate the point.


>
>1. The result of plus is returned via address of a newly created
>object. Why?

Why not? Returning an object is what you must do if you want to be
dealing with things tha have more than just numeric behaviour; the
example would become too large and the essential issue would be lost
if I added some other behaviours. As for returning a new instance, it
takes less code in this short example; in practice you would consider
finding an existing instance that correctly represents the desired
value, but would also consider whether or not the maintenance of the
set of created values is worth the cost.

>2. The second argument is passed via address. Why?

I chose to create the objects on the stack rather than with an
explicit 'new'. Passing objects by pointer seems to be the
conventional way to do it in C++, and you have to take the address of
a stack allocated object to do this.

>3. I::plus is dispatching on the first argument. Why?

In the classical view, the target object is being asked to dispatch on
the the name of the method.

>4. I::plus is not dispatching on the second argument. Why?

This concept does not make any sense in the classical view.

>5. BI:plus overrides I::plus. To make it a modular integer? But
>subranges have no sense for modular arithmetic. Also, why?

The real point is that it is a type that appears to use a subset of
the values of some other type, and some will incorrectly call such
things subtypes.

>
>All "whys" have only one goal, that's achieving suprises. Your example
>proves the well known saying that a real FORTRAN program can be written
>in any language (:-))

When constructing a small example that illustrates an issue that may
arise in a large system, it is usual to include only the essential
elements in order to make the example small enough and simple enough
to be analysed. It is always possible to write the unimportant aspects
of the example in a different way so as to avoid the issue. This does
nothing to help in understanding how to avoid the problem in large
systems.

My FORTRAN is somewhat out of date; FORTRAN 77 is the most recent
version I have studied in any detail. As far as I remember, FORTRAN 77
does not have a type system capable of creating this example, and if
later versions have added features that make this example possible,
then the old idea that FORTRAN programs are necessarily
unsophisticated must be discarded.

I would say it is more in the style of Smalltalk than of FORTRAN, but
perhaps a Smalltalk expert could comment on this if they disagree.

>Your example functions because:
>
>1. Plus is made dispatching. You should explain the reason why it have
>to be one.

It is an illustration of one of the consequences of making everything
in the language an object in the classical object model. If you want
to unify the built-in and user-defined types, you have to face this
kind of issue.

>2. Even if Plus MUST be dispatching, then being a binary function
>implementing a SYMMETRICAL operation, it should dispatch on both
>arguments. You made it dispatching on only the first argument. Arguing
>that C++ has only single dispatch leads us to the conclusion about an
>insufficient language design.

This is a feature of the classical object model, and any
implementation of that model will be like this. Note that this is well
matched to the use of objects in distributed systems.

>3. If a method is dispatching on several arguments, and a subtype
>overrides it, then all signatures involving the new type should be
>overriden to avoid "surprises". That is what your example actually
>shows. In a well designed language the compiler should warn the
>programmer in such cases.

This makes sense only in a generic function view. Perhaps you could be
explicit about which other signatures should have been overriden.

>
>> >Yes, overriding may have strange effects. Multiple dispatch could be
>> >extremely dangerous, if not somehow limited (I do not know, how)
>>
>> I created a type that was a subtype within the rules of the language
>> (it let me use it in place of the supertype), but which was not an LSP
>> subtype. What mechanism I used to do this is beside the point. LSP
>> tells us that what I did is bad because it can surprise someone who
>> has no knowledge of my type, and who ought to need no knowledge of my
>> type. LSP does not tell me which mechanisms I should or should not
>> use, it just tells me what I should or should not do with them.
>
>In other words we should choose between LSP and dispatching methods.
>Right?

No. My reading of the relevant papers suggests that LSP was created in
an essentially classical object view of the world, and one in which
all methods are what you call dispatching. In particular, Emerald is
cited as an example of a language with a type system that is not
strong enough, not one that has an inappropriate model. If you have
not studied Emerald, you should; it has a type system that is less
strict than LSP, but which does have well defined and useful
properties.

>
>BTW. I do not see how your example violates LSP. It seems that in your
>opinion any overriding violates it:

Overriding is one of the first places you look for LSP violations, but
overriding does not in itself mean that LSP is violated. It all
depends what behaviour you define for the overriding method.

>
>class A
>{
> virtual void Foo (); // Makes something extremely useful
>};
>
>class B
>{
> virtual void Foo () { SystemShutdown (); }
>};
>
>Does this violate LSP?

No. You have not asserted that one is a subtype of the other. If you
were to assert that one is a subtype of the other I would say that the
relationship is not subtyping as defined by LSP. If you choose to call
this a violation of LSP then that would be your choice of words, I
would say that neither is a subtype of the other by the LSP
definition, so it would be an error to use one as if it were the
other.


>
>If LSP is so worth, then let's enforce it.

I think LSP is a useful design principle, but I have no desire to
enforce it mechanically in programming languages.

> Let's give it a precise
>definition in terms of a programming language, and then trow out of the
>language all the constructs violating LSP. The question is how much of
>the given language would remain?

If you read the relevant papers, you will find that there is already a
formal definition of the behavioural notion of subtyping that has
become known as LSP. It is not clear whether or not it would be useful
to incorporate this into a programming language. If one were to do so,
the impact would be on the built in or library types where they are
considered to be in a subtype relationship, and on the users' ability
to declare certain types as being related by subtyping. I don't see
anything that would need to be taken out of a programming language,
you just have to think more carefully about types that are related by
something that is not quite subtyping.

dmitr...@my-deja.com

unread,
Nov 20, 2000, 3:00:00 AM11/20/00
to
In article <3a186835...@news.waitrose.com>,
owen...@waitrose.deletethis.com (Owen Rees) wrote:

> You still do not seem to have grasped the idea of a type that is the
> set of integers between 1 and 10 inclusive, with a defined set of
> operations over that set, without any reference to the variables we
> might need later in order to hold values of that type.

A type cannot be a set of integers. A type can only correspond to a set
of integers like.

type OneToTen is range 1..10;

Then if you define a type, you automatically have an ability to have
variables of that type.

> This is
> elementary stuff when you study abstract algebra; you consider whether
> what you have is a group, a ring, an integral domain, a field etc.

Algebra is not programming. It does not operate variables and types.

> If you choose to bring subranges into the discussion, you should
> expect those who have studied some mathematics to ask just what kind
> of algebraic structure you are writing about.

What for?

subtype Buffer_Index is Integer range 1..256;

is not about algebra.

> It turns out that you were
> not writing about a subrange of integers type, but merely about
> variables that reject values outside a certain range.

A variable cannot reject something.

The concept of constrained subtypes is widely used in Ada. Take a look
at ARM.

> >> You will have to explain this again. To get you started, here is a
> >> complete example (in C++) that exhibits the behaviour (and yes it
> >> leaks storage, but I don't care in this example):
> >>
> >> #include <iostream.h>
> >>
> >> class I
> >> {
> >> public:
> >> int value;
> >> I(int ival) : value(ival) { };
> >> bool equal(I *i) { return value == i->value; };
> >> virtual I* plus(I *i) { return new I(value+i->value); };
> >--------^^^^^^^
> >I::plus is dispatching on the first (hidden) parameter
>

> You end up with a very different world view if you think of
> invocations as being directed to a function that dispatches on the
> type of its arguments (generic function), rather than thinking of
> invocations being directed to object instances that dispatch on the
> name of the method (classical).

What you call classical model is a case of what you call generic
function model, where dispatch is limited by the first argument. This
limitation has no "physical" reason.

> The classical vs. generic function debate is probably as old as the
> square/circle as a subtype (or not) of rectangle/ellipse.
>
> If your world view is generic function based then I can see how a
> behavioural notion of subtyping would seem strange, but it is very
> natural in the classical model.

The classical model cannot cope with binary polymorphic methods.
Therefore, either the model is insufficient or I::plus should be
non-dispatching.

> >The surprising behaviour is achieved due to multiple errors in the
> >type design:
>
> Not errors, choices to illustrate the point.
> >
> >1. The result of plus is returned via address of a newly created
> >object. Why?
>
> Why not? Returning an object is what you must do if you want to be
> dealing with things tha have more than just numeric behaviour; the
> example would become too large and the essential issue would be lost
> if I added some other behaviours. As for returning a new instance, it
> takes less code in this short example; in practice you would consider
> finding an existing instance that correctly represents the desired
> value, but would also consider whether or not the maintenance of the
> set of created values is worth the cost.

It should return a value. Pointers should be avoided.

> >2. The second argument is passed via address. Why?
>
> I chose to create the objects on the stack rather than with an
> explicit 'new'. Passing objects by pointer seems to be the
> conventional way to do it in C++, and you have to take the address of
> a stack allocated object to do this.

The correct way is to pass it ether by reference or by value. Latter is
preferable for performance reasons.

> >3. I::plus is dispatching on the first argument. Why?
>
> In the classical view, the target object is being asked to dispatch on
> the the name of the method.

There is no good reason in dispatching "+" for numeric types. Derived
types will never override it.

> >4. I::plus is not dispatching on the second argument. Why?
>
> This concept does not make any sense in the classical view.

Why it has sense for the first argument and has no sense for the second.
Plus is a symmetric operation. Note that numeric plus is also
commutative. It means that the model should ensure that A+B "behaves"
like B+A. Your model intentionally violates this requirement.

> >5. BI:plus overrides I::plus. To make it a modular integer? But
> >subranges have no sense for modular arithmetic. Also, why?
>
> The real point is that it is a type that appears to use a subset of
> the values of some other type, and some will incorrectly call such
> things subtypes.

There is absolutely no reason why BI should override plus. If it has
another semantic of plus, then it is very suspicious to have it derived
from I.

> >All "whys" have only one goal, that's achieving suprises. Your
> >example proves the well known saying that a real FORTRAN program can
> >be written in any language (:-))
>

> My FORTRAN is somewhat out of date; FORTRAN 77 is the most recent
> version I have studied in any detail. As far as I remember, FORTRAN 77
> does not have a type system capable of creating this example, and if
> later versions have added features that make this example possible,
> then the old idea that FORTRAN programs are necessarily
> unsophisticated must be discarded.

This was just a quote from a famous old paper published in Datamation.

> >Your example functions because:
> >
> >1. Plus is made dispatching. You should explain the reason why it
> >have to be one.
>
> It is an illustration of one of the consequences of making everything
> in the language an object in the classical object model. If you want
> to unify the built-in and user-defined types, you have to face this
> kind of issue.

How this require all arguments of all subroutines be dispatching?

> >2. Even if Plus MUST be dispatching, then being a binary function
> >implementing a SYMMETRICAL operation, it should dispatch on both
> >arguments. You made it dispatching on only the first argument.
> >Arguing that C++ has only single dispatch leads us to the conclusion
> >about an insufficient language design.
>
> This is a feature of the classical object model, and any
> implementation of that model will be like this.

OK, then an insufficient model.

> Note that this is well
> matched to the use of objects in distributed systems.

I can imagine a distributed system using multiple dispatch.

> >3. If a method is dispatching on several arguments, and a subtype
> >overrides it, then all signatures involving the new type should be
> >overriden to avoid "surprises". That is what your example actually
> >shows. In a well designed language the compiler should warn the
> >programmer in such cases.
>
> This makes sense only in a generic function view. Perhaps you could be
> explicit about which other signatures should have been overriden.

All signatures that involve the new type. If BI overrides "+" of I, it
should override:

BI+I, I+BI, BI+BI.

> Overriding is one of the first places you look for LSP violations, but
> overriding does not in itself mean that LSP is violated. It all
> depends what behaviour you define for the overriding method.

Behaviour is not a language term. Either a language construct never
violates LSP or not.

> >If LSP is so worth, then let's enforce it.
>
> I think LSP is a useful design principle, but I have no desire to
> enforce it mechanically in programming languages.

So it must not be used as a goal of language design.

AGREED!

Daniel T.

unread,
Nov 20, 2000, 3:00:00 AM11/20/00
to
In article <8v8l89$b0j$1...@nnrp1.deja.com>, dmitr...@my-deja.com wrote:

> BTW. I do not see how your example violates LSP. It seems that in your
> opinion any overriding violates it:
>
> class A
> {
> virtual void Foo (); // Makes something extremely useful
> };
>
> class B
> {
> virtual void Foo () { SystemShutdown (); }
> };
>
> Does this violate LSP?
>
> If LSP is so worth, then let's enforce it. Let's give it a precise
> definition in terms of a programming language, and then trow out of the
> language all the constructs violating LSP. The question is how much of
> the given language would remain?

If I may be so bold as to enter the conversation... Eiffel already has
done the above (except for one issue) and what remains is a solid OO
language and the principle of Design by Contract.

One cannot say if your example above, or Owen's original example, violates
LSP without first defining what the function was supposed to accomplish
(ie its contract.) Once that is done, Eiffel enforces that contract among
all sub-types thereby enforcing LSP among those sub-types.

Owen Rees

unread,
Nov 20, 2000, 8:16:45 PM11/20/00
to
On Mon, 20 Nov 2000 13:08:07 GMT, dmitr...@my-deja.com wrote:

>In article <3a186835...@news.waitrose.com>,
> owen...@waitrose.deletethis.com (Owen Rees) wrote:
>
>> You still do not seem to have grasped the idea of a type that is the
>> set of integers between 1 and 10 inclusive, with a defined set of
>> operations over that set, without any reference to the variables we
>> might need later in order to hold values of that type.
>
>A type cannot be a set of integers. A type can only correspond to a set
>of integers like.
>
>type OneToTen is range 1..10;
>
>Then if you define a type, you automatically have an ability to have
>variables of that type.

"A type is characterized by a set of values and a set of operations."
(ANSI/MIL-STD-1815A-1983 section 3.3). I just want the freedom to
choose a set of values and the operations over those values in
whatever way suits my need.

>
>> This is
>> elementary stuff when you study abstract algebra; you consider whether
>> what you have is a group, a ring, an integral domain, a field etc.
>
>Algebra is not programming. It does not operate variables and types.

I want to be able to define a type that corresponds to an algebraic
structure. For example, I might want to define a type that performs
arithmetic in the Galois field with eleven elements. I might want to
define a type that performs arithmetic over discrete elliptic curves.


>
>> If you choose to bring subranges into the discussion, you should
>> expect those who have studied some mathematics to ask just what kind
>> of algebraic structure you are writing about.
>
>What for?
>
>subtype Buffer_Index is Integer range 1..256;
>
>is not about algebra.

Maybe I am trying to use a type with a non-standard set of integer
values, and operations defined the way I need over those values, to
try to become famous for finding the factorisation algorithm or
discrete logarithm algorithm that breaks public key cryptography.

>
>> It turns out that you were
>> not writing about a subrange of integers type, but merely about
>> variables that reject values outside a certain range.
>
>A variable cannot reject something.
>
>The concept of constrained subtypes is widely used in Ada. Take a look
>at ARM.

The citation above should reveal that I have looked at what I assume
you mean by ARM (at least the 1983 version of it). I am not impressed
by the way operations are defined for subtypes because they are not
defined over the subtypes but over the base types, and this undermines
the notion of subtypes as types in their own right.

>> You end up with a very different world view if you think of
>> invocations as being directed to a function that dispatches on the
>> type of its arguments (generic function), rather than thinking of
>> invocations being directed to object instances that dispatch on the
>> name of the method (classical).
>
>What you call classical model is a case of what you call generic
>function model, where dispatch is limited by the first argument. This
>limitation has no "physical" reason.

That is a traditional argument put forward by those who favour the
generic function model, and is no better (or worse) now than it was
when it was first used many years ago. Those who prefer the classical
model usually respond that the classical object model has a strong
notion of encapsulation that is absent from the GF model, and so GF is
not really about objects at all. The debate then continues down a well
worn path.

>> Why not? Returning an object is what you must do if you want to be
>> dealing with things tha have more than just numeric behaviour; the
>> example would become too large and the essential issue would be lost
>> if I added some other behaviours. As for returning a new instance, it
>> takes less code in this short example; in practice you would consider
>> finding an existing instance that correctly represents the desired
>> value, but would also consider whether or not the maintenance of the
>> set of created values is worth the cost.
>
>It should return a value. Pointers should be avoided.
>
>> >2. The second argument is passed via address. Why?
>>
>> I chose to create the objects on the stack rather than with an
>> explicit 'new'. Passing objects by pointer seems to be the
>> conventional way to do it in C++, and you have to take the address of
>> a stack allocated object to do this.
>
>The correct way is to pass it ether by reference or by value. Latter is
>preferable for performance reasons.

If you don't like pointers, let's have a slightly modified example,
this time in Java with no 'virtual' keyword, no pointers and no
address operators, but the same demonstration that LSP is not
enforced:

class I {
public I(int i) { val = i; };
public int v() { return val; };
public I plus(I i) { return new I(val+i.val); };
protected int val;
}

class useI {
public void test(I i1, I i2) {
System.out.println(i1.v() + "+" + i2.v() +
"=" + i1.plus(i2).v());
System.out.println(i2.v() + "+" + i1.v() +
"=" + i2.plus(i1).v());
};
};

class Zp extends I {
public Zp(int i) { super(i%11); };
public I plus(I i) { return new I((val+i.val)%11); };
};

public class subtype {
public static void main(String[] args) {
I i = new I(6);
Zp b = new Zp(6);
useI u = new useI();
u.test(i,b);
}
}

>
>> >3. I::plus is dispatching on the first argument. Why?
>>
>> In the classical view, the target object is being asked to dispatch on
>> the the name of the method.
>
>There is no good reason in dispatching "+" for numeric types. Derived
>types will never override it.

I might choose to do so. Why should it be prohibited?

>
>> >4. I::plus is not dispatching on the second argument. Why?
>>
>> This concept does not make any sense in the classical view.
>
>Why it has sense for the first argument and has no sense for the second.
>Plus is a symmetric operation. Note that numeric plus is also
>commutative. It means that the model should ensure that A+B "behaves"
>like B+A. Your model intentionally violates this requirement.

The example is sufficiently small that you can see that this has
happened. Since both the C++ and Java versions pass their respective
type checks, it should serve as a warning that problems of this kind
can arise in systems that are large enough that it is not obvious what
has happened.

>
>> >5. BI:plus overrides I::plus. To make it a modular integer? But
>> >subranges have no sense for modular arithmetic. Also, why?
>>
>> The real point is that it is a type that appears to use a subset of
>> the values of some other type, and some will incorrectly call such
>> things subtypes.
>
>There is absolutely no reason why BI should override plus. If it has
>another semantic of plus, then it is very suspicious to have it derived
>from I.

Should the language be defined to prohibit overriding with
incompatible semantics? If so, should compilers detect this and report
the error?

>
>> >All "whys" have only one goal, that's achieving suprises. Your
>> >example proves the well known saying that a real FORTRAN program can
>> >be written in any language (:-))
>>
>> My FORTRAN is somewhat out of date; FORTRAN 77 is the most recent
>> version I have studied in any detail. As far as I remember, FORTRAN 77
>> does not have a type system capable of creating this example, and if
>> later versions have added features that make this example possible,
>> then the old idea that FORTRAN programs are necessarily
>> unsophisticated must be discarded.
>
>This was just a quote from a famous old paper published in Datamation.

In what way was the quote relevant?


>
>> >Your example functions because:
>> >
>> >1. Plus is made dispatching. You should explain the reason why it
>> >have to be one.
>>
>> It is an illustration of one of the consequences of making everything
>> in the language an object in the classical object model. If you want
>> to unify the built-in and user-defined types, you have to face this
>> kind of issue.
>
>How this require all arguments of all subroutines be dispatching?

In the classical model, arguments are not dispatching. My example
illustrates one small issue that arises if you treat numbers as
objects in the classical model, so there is no dispatching on
arguments in it.


>
>> >2. Even if Plus MUST be dispatching, then being a binary function
>> >implementing a SYMMETRICAL operation, it should dispatch on both
>> >arguments. You made it dispatching on only the first argument.
>> >Arguing that C++ has only single dispatch leads us to the conclusion
>> >about an insufficient language design.
>>
>> This is a feature of the classical object model, and any
>> implementation of that model will be like this.
>
>OK, then an insufficient model.

Insufficient for what purpose, and by comparison with what? The
asymmetry is a consequence of the encapsulation that you get with the
classical model. A model that has symmetry but not encapsulation is
also insufficient, so you have to choose the lesser of two evils for
whatever purpose you have at hand.


>
>> Note that this is well
>> matched to the use of objects in distributed systems.
>
>I can imagine a distributed system using multiple dispatch.

I can name a distributed system that works in a way that is
essentially multiple dispatch, even though it is not described in
those terms. I also know at least in outline the strengths and
weaknesses of that system as compared with those that are based on the
classical model. I know which has been more widely used in practice.
What point are you making here?

>
>> >3. If a method is dispatching on several arguments, and a subtype
>> >overrides it, then all signatures involving the new type should be
>> >overriden to avoid "surprises". That is what your example actually
>> >shows. In a well designed language the compiler should warn the
>> >programmer in such cases.
>>
>> This makes sense only in a generic function view. Perhaps you could be
>> explicit about which other signatures should have been overriden.
>
>All signatures that involve the new type. If BI overrides "+" of I, it
>should override:
>
>BI+I, I+BI, BI+BI.

Perhaps you could show me how you would do that in either C++ or Java.

sk...@my-deja.com

unread,
Nov 21, 2000, 3:00:00 AM11/21/00
to
In article <3a15ccae...@news.waitrose.com>,
owen...@waitrose.deletethis.com (Owen Rees) wrote:

This depends on the *behaviour* you want your s/w hierarchy to have.
This is the key to understanding subtyping, and the implications
thereof. And here comes the key question :

1) What degree of compatibility do you want between I and BI ??

a) If you want "complete" compatibility (LSP etc) , then I and BI
cannot achieve it "as is" . Some mutator ops are incompatible.

b) If you want such compatibility and correctness you must address the
semantics of types and mutator ops accordingly.


The decision i would thus make is :

class I
{

/* The semantics of this class is that all "mutator" ops leave values
of this type in the range [L,U] . The values of L/U may be refined
accordingly by subtypes. For I, the most general range is :
[ -INFINITY, INFINITY] .
*/

I(i: int) raises BoundsErr ;
I(i: I) raises BoundsErr ;


// POST : If the sum (this.val() + i.val() ) is not in the range [L,U]
// then I::plus raises BoundsError.

virtual
I
plus(i: I) raises BoundsErr ;

// ...
}


This gives me the behaviour more akin to 1b.
Implementors of subtypes enforce the appropriate semantics.
Users of 'class I' are explicitly informed that the plus op *may*
raise an exception. Therefore they should use the op accordingly.

***

As an aside, what I desire often is a notion of "class" exception, to
indicate formally that all or some of the usage of a class can raise
one or more common exceptions. For example, using the uml stereotype
and OCL "notation" :

I: op assign ... <<mutator>>
I: op plus ... <<mutator>>

INV(I) : forall o in { op in I.ops : op.stereotype = mutator } :
BoundsErr in o.exceptions

ie all mutator ops in 'class I' can raise the BoundsErr exception.

***


>Mutating ops certainly cause problems, but you can have problems
>without mutating ops.

I have stated this before (and thus is already acknowledged) , so I
won't address it again. But in the context of *this* thread (subtyping
for numerics - in fact, "subset" subtyping) , the mutable properties
cause the problems (more so when an inadequate specification of 'plus'
has been given) .


>Suppose we create objects that represent values:

>class I
>{
> bool equal(I i);
> I plus(I i); // returns an object representing the value this+i
> // ...
>}

>class BoundedI<int L, int U> : public I
>{
> I plus (I i); // returns (this-L+i)%(U-L)+L silently wrapping
> // ...
>}

>typedef BoundedI<1,10> BI ;

>BI b(6); // the BI that represents 6
>I i(6);
>I i2 = b; // use subclass as class
>// ... somewhere later, inside some other method

>i2.plus(i).equal(i.plus(i2)); // is false ***

>Here, by passing a subrange value object, we have made it matter which
>way round a programmer who knows nothing of BI, and wrote the code
>before BI existed, has to add two instances of type I, and there is no
>right answer because we could have passed things the other way round.

Absolutely. Just as insidious as square/rectangle.
And still just as surmountable.

I have given the reasons why in a reply to one of your later postings in
this thread (NB: you'll need to bear in mind my above definition for
class I) .

>>However, BI is read-only substitutable with I (I can compare their
>>values etc) . This is a weaker form of the LSP, but LSP-conformant
>>nonetheless. And useful too.

>I think my example demonstrates LSP violation with no mutable things
>anywhere, and also shows how this can have effects that propagate into
>obscure places with surprising effects.

This wasn't the point being addressed by me.
What I demonstrate is that :

1) I and BI are not substitutable because of their mutating ops.
I::plus is one such mutating op.

2) The int subrange example is a very common instance of what can be
termed "subset" subtype relationships.

3) Subset subtyping nearly always causes semantic incompatibility,
usually due to mutator ops. LSP violation often occurs as one
attempts (incorrectly) to define one set of common mutator ops
across whole class hierarchies.

4) LSP-conformance for subset subtyping is usually only achievable via
R/O substitutability.

Regards,
Steven Perryman

It is loading more messages.
0 new messages