Re: Mixing OO and DB

From: Robert Martin <unclebob_at_objectmentor.com>
Date: Thu, 21 Feb 2008 13:03:27 -0600
Message-ID: <2008022113032797157-unclebob_at_objectmentorcom>


On 2008-02-20 20:27:28 -0600, David BL <davidbl_at_iinet.net.au> said:

> On Feb 20, 11:32 pm, Robert Martin <uncle..._at_objectmentor.com> wrote:
>
> I thought you were saying before that S is a subtype of T if
> substitutability holds for all P. Now you are saying the subtype
> relationship is a function of P. Which is it?

The latter. If S is substitutable for T in P, then from P's point of view S is a subtype of T.

>> C++ programmers know that a circle is an ellipse.

>
> Sure they do. I was being facetious. I am a C++ programmer.
>
>> They also know that
>> a program that represents a circle is not necessarily a program that
>> represents an ellipse -- for the same reason that vector<circle> is not
>> a subtype of vector<ellipse>.

>
> How does a program represent an ellipse? I would prefer it if you
> could be more careful with your terminology!

I think you know the answer to that. The source code in ellipse.{h,cpp} that defines the C++ class named ellipse *represents* an ellipse at runtime; but is not an ellipse.

>

>> 
>>> Eg suppose Circle subtypes Ellipse then
>> 
>>> Ellipse e;
>>> Circle c;
>>> Circle* px = &c;
>>> Ellipse* py = px;  // pointer conversion
>>> *py = e;     // oops
>> 
>> As I said.  Exactly the same reasoning.  A reference to S is not a
>> subtype of a referenct to T.

>
> What has saying that a reference to S is not a subtype of a reference
> to T got to do with it? I thought that was only relevant to the B**,
> D** example.
>
> There is the following rule in C++
>
> S is subtype of T => allow implicit upcast S* to a T*
>
> I'm saying this rule is inappropriate for value types, because it is
> incompatible with Circle subtyping Ellipse. This is one of the main
> reasons why I say C++ doesn't support value types correctly.

I understand. The point is that a C++ class named "Circle" is not a circle. It "refers" to a circle in some sense; but it is not a circle in and of itself. The fact that the slicing rules of C++ make it impossible to properly upcast a Circle value to an Ellipse value is simply evidence that the Circle value is not a subtype of an Ellipse value. Nor should it be since a circle value is not a circle, and an Ellipse value is not an ellipse.

The issue is isomorphic with S** not being a subtype of T** and vector<S> not being a subtype of vector<T>.

References do not inherit the subtyping of their referents.

>> We can define inheritance to mean anything we want.  However, in the OO
>> sense, a complex number composed of two real numbers cannot be the base
>> class of the real numbers.  If it were, then every real number would
>> have two real numbers inside it...

>
> But why would anyone want to treat real or complex numbers "in the OO
> sense". These should obviously be treated as value types not object
> types.

For the same reason that people want to treat circle and ellipse in the OO sense. I once saw a geometry library where a circle was a subtype of arc. Same flaw.

OO classes are "references" to referents in the problem or solution domain. Those referents may have subtype relationships. That does not mean the classes do.

> If you want to treat value types as object types, you will *always*
> find that subtyping is inappropriate amongst value types.

Not if they are substitutable. Again, the address with a zip code is substitutable for an address without one. It works as values just as well as it works as pointers.

>> 
>> struct Address {
>> string street, city, state;
>> 
>> };
>> 
>> struct AddressWithZip : Address {
>> string zip;
>> 
>> }
>> 
>> void f(Address a);
>> 
>> AddressWithZip az;
>> f(az); // fine.  Slices off the zip.  Works great.

>
> I find that extremely ugly. It only takes another small step for the
> following
>
> struct Square { int width; };
> struct Rectangle : Square { int height; }
>
> void f(Square s);
>
> Rectangle r;
> f(r); // fine. Slices off the height. Works great.

But here you have made the error that the class of Square should be a subtype of the class of Rectangle, just because a true square is a true rectangle. That's the flaw! References do not inherit the subtype relationship of their referents.

So, in fact, the two pieces of code are utterly different. In the address case, substitutability is preserved. There is no confusion about references and referents. The only subtype relationship the program cares about is the subtype relationship of the two classes. Whereas in the Rect/Square problem you are trying to assert a subtype relationship between the classes that incorrectly assume exists because there is a subtype relationship between what the two classes represent.  S** is not a subtype of T**. vector<s> is not a subtype of vector<t>.  REFERENCE(S) is not a subtype of REFERENCE(T).
>
>

>>> Here is a "proof":  One expects variables of the supertype to support
>>> assignment to any values of the supertype, hence we deduce that a
>>> subtype is only allowed to contain a superset of all the values in a
>>> supertype.
>> 
>> Fine so far.  subtype values must have every variable that the supertype has.
>> 
>>> Also the function can read the value of the variable as a
>>> value in the supertype, which shows that the values of the subtype
>>> must be a subset of the values in the supertype.
>> 
>> I didn't follow that one.  Subtypes cannot contain subsets of variables
>> of the supertype.

>
> I said values not variables. By definition a value type is a set of
> values plus operators on those values. Let S be a subtype of T. In C
> ++ an S* can be upcast to a T*. Let v denote some value in S stored
> in variable x (of type S). We can take the address of x, upcast it to
> a T* and dereference (to read the value v) and assume it is of type T.
> Therefore we have shown that upcasting pointers implies that the
> values in a subtype must be a subset of the values in a supertype.

You can what? If T does not have v then you cannot create a pointer to a member variable S::v and try to access it on a T*. Sorry, the language won't let you do that. You'd have to cast it back to an S* Good thing too!
>
> Now of course this proof assumed that v wasn't sliced, so I guess you
> will say the proof is flawed.

It was flawed from the get-go. You cannot access a variable defined in a subclass using a pointer to the base class.

> However I find the idea that the
> compiler will silently slice away part of a value and allow you to
> reinterpret it as something else extremely suspicious.

You have to know that it's happening, of course, but other than that it's a perfectly reasonable action for a language to take, and it preserves LSP.

-- 
Robert C. Martin (Uncle Bob)  | email: unclebob_at_objectmentor.com
Object Mentor Inc.            | blog:  www.butunclebob.com
The Agile Transition Experts  | web:   www.objectmentor.com
800-338-6716                  |
Received on Thu Feb 21 2008 - 20:03:27 CET

Original text of this message