Re: Mixing OO and DB

From: David BL <davidbl_at_iinet.net.au>
Date: Thu, 21 Feb 2008 18:43:52 -0800 (PST)
Message-ID: <24e77677-a035-4c7a-9dcd-3bc098a8378e_at_s19g2000prg.googlegroups.com>


On Feb 22, 4:03 am, Robert Martin <uncle..._at_objectmentor.com> wrote:
> On 2008-02-20 20:27:28 -0600, David BL <davi..._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.

This is your justification for not sub-typing circle from 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.

I think you mean a run time instance of the Circle class "refers" to a circle in some sense.

> 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.

Disagree. C++ is broken in that case.

> Nor should it be since a circle value is not a circle, and an
> Ellipse value is not an ellipse.

No, by definition a circle value is a circle etc. Are you associating the word "value" with what C.Date calls an appearance (or encoding if you like) of a value?

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

At best you should say analogous, or are you prepared to define a bijection!

The analogy depends on the idea that a value-type variable that holds an appearance of a value can be regarded as analogous to a "pointer" to a value. I find this analogy a little suspect - because I would reserve the terms "pointer" or "reference" to when there is some concept of an address space and the pointer or reference is to a location in the address space and that is distinct from what is stored at that location.

Values are self-identifying. When a variable holds an appearance of a value there is an encoding involved, but that isn't the same as an indirection in the sense of a pointer dereference.

> References do not inherit the subtyping of their referents.

Sure.

> >> 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.

So do you agree it is a bad idea to treat value-types in the OO sense?

> 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.

I don't understand that at all. In what sense does a class (a compile time definition of a type plus a full or partial implementation) reference something in the problem or solution domain? Let's see you formalise that!

> > 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.

You got it round the wrong way. The above code states (incorrectly) that a Rectangle is a subtype of Square.

> 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).

I'm afraid the above is nonsense to me because I don't understand how a class represents a reference.

I don't even know whether you think the Square/Rectangle code is reasonable or not. I suspect we both strongly agree it isn't. Yes?

Rather than discuss the Square/Rectangle case which I presume we agree on, let me explain more carefully why I believe the Address example is suspect.

You have defined a base class called Address. Presumably this is a value type, meaning that it is associated with an abstract set of all (possible) address values.

From Address you define a subtype called AddressWithZip. As a subtype there is meant to be some sense in which it is a specialisation. I presume you would say informally that a AddressWithZip is-a Address. Agreed?

It would seem valid to assume that within the set of all address values, some have zip codes and some do not. As a type, AddressWithZip is relevant to the addresses that have a zip code. If follows that a zip code is regarded as part of an address value.

Unfortunately C++ will happily (silently) allow the zip code to be sliced away. This means we end up with an Address value without a zip code. Are we to assume that the address doesn't have a zip code. Clearly we can't! Therefore given an instance of class Address we cannot assume it actually locates a particular value in our abstract set of all possible address values. This contradicts the definition of Address as a value-type for all possible address values.

If anything the base class should be called AddressWithoutZip. However it is clearly silly for AddressWithZip to be a subtype of AddressWithoutZip - about as silly as saying a Rectangle is a subtype of Square.

There actually exists a good solution to this problem. The C.Date concept of sub-typing allows for a rich type hierarchy of address types with all the advantages of specialised implementations for subtypes. Unfortunately C++ doesn't support it.

> >>> 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!

Wow there is a huge communication barrier. v is not a member variable. It is a value.

Please read my description again, with the following code in mind

class T { ... };
class S : public T { ... };

S x = v; // v is a value . (r-value in C++ terminology)

S* p = &x;
T* q = p;
T v2 = *q;

No slicing => values in type S are subset of values in type T

[snip] Received on Fri Feb 22 2008 - 03:43:52 CET

Original text of this message