Re: Mixing OO and DB

From: Dmitry A. Kazakov <mailbox_at_dmitry-kazakov.de>
Date: Mon, 18 Feb 2008 11:23:57 +0100
Message-ID: <1pk38wl97ij8s$.uo99w5t8s33i$.dlg_at_40tude.net>


On Sun, 17 Feb 2008 21:56:18 -0800 (PST), David BL wrote:

> On Feb 16, 9:03 pm, "Dmitry A. Kazakov" <mail..._at_dmitry-kazakov.de>
> wrote:

>> I mean that you need not to resort to pointers or more generally to
>> internal representation in order to say that Rectangle value can be
>> obtained from a ColouredRectangle one.

>
> Agreed. However note firstly that I introduced pointers in order to
> talk about substitutability of variables. Secondly, I don't deny it's
> *possible* to obtain a Rectangle value from a ColouredRectangle one.
> I don't think this should ever happen implicitly.

Why not, If you can do it safely?

>>> Perhaps the C.Date approach needs a different terminology (ie than
>>> subtype) to avoid this confusion.   When Date says integers is a sub-
>>> type of reals, he means for example that nonzero integers have
>>> inherited real-valued multiplicative inverses.  In mathematical
>>> definitions of integers that's inappropriate, but for software
>>> development I need to see a good reason to disallow it.  Can you
>>> provide one?
>>
>> No, it is OK. That is a specialization. You constrain a type upon
>> subtyping. The constraining manifests by loosing some operations. There is
>> nothing wrong with that. But also there is nothing wrong with
>> generalization. And also there is nothing wrong with mixed cases.

>
> With C.Date's notion of sub-type you always inherit all operations
> from the super-type. You can add more operations, but cannot lose any
> (or redefine any).
>
> For example, if 16 bit integers subtype 32 bit integers, then 16 bit
> integers inherit addition modulo 2^32 (stored in a 32 bit result).
> That doesn't stop 16 bit integers from introducing addition modulo
> 2^16 as a distinct operator. Overloading of operator+ is just a
> syntactic issue.

It is not. The user of 16-bit integer has to know which + to take. As a counter example consider big and little endian encoded integers. One is a subtype of another. Now you have two overloaded operations + (actually more for mixed cases) one of them is rubbish.

Note. What C.Date aims at, is easily solved by the position 4 below. When you dispatch to 2^32 operation, it is a 2^32 value there. No further dispatch happens and the proper + gets selected.

>>> I don't think you need the mapping to give an ellipse value from a
>>> circle value.  I would rather say the set of circle values is a subset
>>> of the set of ellipse values.
>>
>> Disagree, that would heavily limit the system, and likely make it
>> inconsistent. The only sense in which circle value should ellipse value is
>> to keep the internal representation same. Why should I care?

>
> It doesn't limit the system in the way you suggest. You can have many
> different representations of the same (logical) value. For example
> polar versus cartesian representation of a point.

OK, but then "same logical value" is merely a class of equivalence of some "physical values." My point is that the latter are fundamental while the former are derived. Note that this is same as in mathematics where Q "contains" Z, while members of Q aren't numbers at all, but sets of ordered pairs.

> I can imagine a language with the same goals for run time efficiency
> as C++, and the following function can be compiled for many different
> implementations of Rectangle, including specialisations like Square or
> UnitSquare.
>
> float GetArea(Rectangle r)
> {
> return r.Width() * r.Height();
> }

Yes, but to be able to judge about *different* representations you have to make them distinct things => different values. First you have shown equivalence, you can forget about differences. But not before that.

>>>> As for subtyping, to me, it is just a relation between types. I don't
>>>> expect it to deliver substitutability. It cannot. What it can and should,
>>>> is to help me to deal with the problem.
>>
>>> Can it?  I'm interested to know what you have in mind.
>>
>> There are many cases where languages could be safer. For example:
>>
>> 1. multiple dispatching should ensure overriding of all cross variants
>> (dispatch may not fail).

>
> I wonder whether dynamic dispatch is only relevant to the LSP notion
> of sub-typing, ie not to value types. Perhaps not, because union
> value-types seem to need it.

If you want to have classes of values, these will be polymorphic values, which shall dispatch. Consider:

class Boolean
{
public :

   virtual String Image () // returns "true" or "false" };

class Tristate : public Boolean
{
public :

   virtual String Image (); // returns "true", "false", "unknown" };

>> 2. construction model shall be sane, i.e. allowing dispatch from
>> constructors/destructors.

>
> This doesn't seem appropriate for value-types. C.Date uses the term
> "selector" instead of constructor as a reminder that it's only purpose
> is to specify a value.

But to specify might mean a hell lot of work. Consider the following:

class Prime { ... };

const Prime X (45259321435634673211);

can you tell if 45...11 is prime? The point is that immutability does not imply no need to compute (and thus construct, and also destruct). Each object denoting a value has to be elaborated and finalized. These actions might be empty, or might be not.

>> 3. there should be an operation extension model, rather than override-whole
>> only.

>
> Not sure what that means.

class T
{
public :

   virtual void Foo ();
};

A derived type may either override or inherit Foo. So the base type T cannot enforce its behavior in Foo as for instance it could in the constructor and destructor.

Basically any polymorphic operation like virtual Foo is defined on the class of types derived from T. Its body is a dispatching switch with cases represented by overridden or else inherited bodies for specific types from the class. This is too limiting model of class-wide body composition.

>> 4. no re-dispatch allowed

>
> What's re-dispatch?

class T
{
public :

   virtual void Foo ();
   virtual void Bar () { Foo (); // This dispatches again } };

If S inherits Bar, and overrides Foo, then re-dispatch will cause S do behave differently in inherited Bar. This is a contract violation.

>> 6. dynamically constrained types (light-weight classes)

>
> So types are not necessarily a static (compile time) concept?

Types are static, their constraints aren't. Consider String. It is constrained by the length of its body. The constraint itself may vary.  

>> 7. abstract interfaces for anything, including member variables, arrays,
>> pointers.

>
> Not entirely sure what that means exactly.

The notations like

A.B
A[B]
&B
*B

shall be virtual operations of some abstract types (interfaces). You could implement these by any given type. So if you wanted your type to appear as a pointer you could do it by implementing the abstract pointer interface. No hard-wired composite types.

>> 8. interface inheritance from concrete types

>
> You can inherit the interface, and only the interface from a concrete
> type?

Yes, or equivalently I can drop the implementation if I wished to. For example a Circle could drop Ellipse's axes and replace them by single radius. BTW, this is the reason why I strongly oppose treating Circle and Ellipse values same.

> Sounds interesting, but what does it mean for the principle
> that details depend on simple abstractions, not vice versa?

I am not sure what you mean here.  

>> 9. supertyping of concrete types

>
> That means different things for different notions of sub-typing.

But to me they all are unified.

-- 
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de
Received on Mon Feb 18 2008 - 11:23:57 CET

Original text of this message