Re: Question on LSP



Responding to Kazakov...

But in an OO context T1 and T2 /are/ disjoint concepts by definition.


Linked list and array are types to model the same concept. Why cannot they
be subtypes of each other?

The could be, but they would not be siblings in the subtyping tree then. We are talking about sibling subtypes in an existing tree.

BTW, I can readily show that T1 and T2 are subtypes of T using standard notations like a tree or a Venn Diagram. How do you show that T1 and T2 are /also/ subtypes of each other in such notations? The best you can do is show overlap of T1 and T2 in a Venn Diagram. But in an OO context T1 and T2 must be disjoint. Besides, it wouldn't prove anything about their being subtypes; at best it defines yet another type derived from them via composition.

But double dispatch is not readily implemented in all implementation environments. The OO paradigm needs to be implementable in all environments. Moreover, it must be implementable in an unambiguous manner so if one must bootstrap infrastructure to support double dispatch, one opens the can of worms for validating the infrastructure.

But same could be said about single dispatch and anything else. How OO
paradigm could depend on some lame languages?

Single dispatch is always implementable at the 3GL level because all 3GLs employ procedural block structuring, message passing, and scope.


It is not essential to single dispatch. Pass a message to a tuple, that's
all. In fact, message passing with multiple dispatch is simpler, because
you can treat all parameters uniformly.

At that level one already does that in OOA/D collaboration since messages are sent to objects (i.e., a tuple of logically related properties), rather than to individual properties. But the actual dispatch paradigm must always resolve the message to a single property at the object level to be consistent with procedural message passing.

The issue is that one can always implement a single dispatch specification with double dispatch but the converse is not necessarily true. So the OOA/D specification needs to use the most general paradigm.

But I don't define what it is. Subtyping is a relation which does not
consider construction or the origin of types. It is merely an ability to
get at the methods. Because (to me) there is no properties beyond methods,
this immediately implies properties sharing.

Nonetheless types in general and subtypes in particular need to be defined with a consistent meta-semantics and there need to be rules about how they relate to one another. The type of '*' is independent of the type of 'T' and there can be no subtyping or substitution between those types.


Hmm, there is no type "*". "T*" is indivisible. "*" is a types algebra
operation, one level above. When applied to a given type T it yields
another type "T*".

I disagree that they are indivisible; that is pretty much my point. The type of '*' is Object Reference. 'T' represents the type of some object being targeted by the reference. But that is not known until the Object Reference type is instantiated; yet the semantics of Object Reference must exist in the language meta-model prior to instantiation. If 'T' is not known until instantiation, then one cannot define T* in the meta model as a single semantic element.

[Actually one could if one had some sort of Composite Type semantic element in the meta-model. But 'T' and '*' would still be unique, identifiable type members coupled by that Composite Type. In fact, I would bet that is exactly what the language is doing with T* syntax.]

The 'T' is just a syntactic marriage (composite of distinct types) so that the developer knows what type the reference was instantiated to point to.

Just out of curiosity, if you think of types solely in terms of methods, how do you define 'object'?


My God, what a question!! (:-))

I never seriously tried to define it formally. A language object should be
something like an annotated value, a type instance. Technically it could be
an ordered pair (T, x). T is a type known statically. x is object's value
of T. At run-time there should be a group of states associated with x,
namely those where the relation hods.

That's what I suspected and therein lies the problem. B-) You are discussing types here -- on a OO forum -- without any regard for the problem space. Types for an OOPL must necessarily reflect the semantics of the OO paradigm.

But I don't want this. I want 1-1 mapping. So pointer is bound to a class
type. This makes it polymorphic, but it still exactly two types in the
relation.

It is only bound to a class type through instantiation, which was my point (4). Because that binding is 1:1, it eliminates any polymorphism that might have been possible at the type level.


Polymorphism remains because it is a class, no specific type of the class
is bound to the pointer until run-time. Example, in Ada:

type T is tagged ... -- Some type
type T_Ptr is access T: -- Non-polymorphic pointer
type Class_T_Ptr is access T'Class; -- Polymorphic pointer

type S is new T with ... -- Derived type

X : T_Ptr := new S; -- Type error, can't bind T_Ptr to S!
>
Y : Class_T_Ptr := new S; -- OK, can bind no anything from the class of T.

Operations called on X will not dispatch. Ones called on Y will. (see also
below)

All this is saying is that in Ada one needs a different flavor (subtype) of Object Reference to access polymorphically through a superclass than directly through a leaf subclass. The OO paradigm doesn't make that distinction; a class is a class is a class.

Just out of curiosity, what use is T_Ptr if it can't be assigned to a member of the T superclass? (Remember the OO paradigm says that one cannot instantiate a member of a superclass without specifying the leaf subclass it belongs to.) Do you have some separate mechanism in Ada for assigning T_Ptr to an existing S in hand?

(2) The semantics of Object Reference can be defined without regard to what the types are of the objects to which it is assigned. Those object types are completely orthogonal to the semantics of being an object reference.

I don't want this either. There is no untyped referential semantics.

There has to be that level of generalization. Otherwise one would have to define an infinite number of Object References in the language a priori to accommodate all possible applications.


Yes, but I see no problem. Same is true for all other composite types,
arrays, records etc. You need some types algebra to generate types.
Formally, if you applied that algebra to all atomic types, you would get
all possible types (an infinite number of). It is a normal mathematical
procedure. [Pointers are better than arrays/records or parametric types,
because the latter can produce an uncountable number of types.]

Ah, but this brings us full circle to your assertion above that T* is indivisible as a type. Here you are saying that T* is a composite of distinct types, T and *, which was my original point -- that T and * are different things semantically. Any relationship between them exists purely in the composition, not in subtyping. IOW, composition and subtyping are two quite different things.

By separating the semantics of the Object Reference type from the target object type one has a much more compact and versatile mechanism because the association of types can be deferred to the binding (instantiation). But that only works if the type of Object Reference is completely independent of the target type.


It is a bad idea, because it is untyped. [There might consistency in
question. I suspect, it might be possible to construct antinomies.]

How is it untyped? Quite the contrary, it seems strongly typed. It is an Object Reference. That is a different type than, say, a Value Reference, an object of type T, or anything of any other type.

By ensuring that Object References are non-polymorphic after instantiation, one ensures correct usage and the compiler can even enforce it.

But the reverse is not true. One cannot reconstruct the state machine purely from 3GL method calls without interpreting other things (event queue, STT, naming conventions, etc.). Thus the 3GL type system is actually less stringent about the constraints on collaboration.


It is an unfair comparison. 3GLs capable to describe sets of states, while
a state machine deals with individual states. It is untyped per definition.
The fact that it might have some typed (or whatever) semantics attached to
states is irrelevant.

The point of the elided context was that event identity, property identity, etc. can be unambiguously mapped from the OOA/D into a 3GL type system but that in doing so information is lost and one cannot go from the type system identity back to the OOA/D identity. That's because type systems are severely limited because of compromises with the hardware computational models, such as marrying message and method.

BTW, let's not lose sight of the original point in this subthread, which was your assertion that pointers reference methods. That is just not true in an OO context.


Of course they do, how else? Each object references its methods! See my
attempt to define "object" above. The first element of the pair is the
type. Each type references its methods, so the object does. [ Isn't it one
of OO corner-stones? ]

Au contraire! A pointer points only to an object and messages are sent to objects. A separate mapping -- at the object level -- is required to map message identity into specific attributes or methods. What the 3GL type systems do is to "hard-wire" the mapping of message identity to procedure identity. It is one of the major compromises that the OOPLs make between the OO paradigm and the hardware computational models.

Note that in UML, the class interface can be defined separately from the class itself and that Interface element explicitly provides a mapping of message identity to method identity. In that case the Interface corresponds to the 3GL type yet it is quite different than the class definition, which defines the actual methods.

In any event, the client conceptually knows nothing about what methods the receiving object has. It creates a message with some identity and it addresses it (usually via a pointer) to the relevant object without any knowledge of what the receiving object will do with it. That decoupling is crucial to preventing implementation dependencies (spaghetti code) in the OO paradigm.

But the OOPL type systems have mucked that up so it is important to solve the problem via OOA/D principles before committing to code. If the OOA/D has eliminated implementation dependencies through the separation of message and method, then it doesn't matter that the OOPL type system subsequently marries them.

My claim is that formally and technically T<-T1 is nothing better than
T<-T*. The case 1, is irrelevant to the issue, because it is covered by
T<-T* (where "class T" is substituted for T).

I strongly disagree with this in an OO context because of (3) above. You are arguing that T* is equivalent to {T1* | T2*}. That is quite true for polymorphic dispatch *IF* T is subclassed.

No, that's the case 1 you are talking about. It is the case 2, for which I
claim that T* "subclasses" T, and the class has types {T, T*}. Further it
also "superclasses" T, because "&" becomes a method of T.

That does not make sense to me, even in a type system. The logical conclusion is that the language must define all the possible types.


Only if you wanted structured types equivalence for pointers. I.e. if you
wished to be able to use T* without prior declaration of. It is a
disputable way, IMO, but it works. I don't expect either structured, or
by-name pointers to impose any problem (see above).

I don't think it is a matter of declarative structure. I don't agree because pointers are pure language implementation artifacts, regardless of the language structure. Lots of languages don't have them at all and one can solve the same problems. If it is a language artifact and it is a unique type, then it must be defined in the language itself. However, T is a problem space type defined by the developer. So if one is going to make T* a subclass of T and define it in the language, then one has to define all /possible/ T*s that the developer might need for arbitrary problem spaces.

I don't see any way around that problem unless one makes T* a hybrid of distinct types.

It can be dynamically instantiated as in 2. And I disagree that time of
bindings may influence semantics. It could be a sugar, but there is
something you want to sweeten...

Where did I mention the time of binding? I just said that the language is able to hide the indirection because a pointer cannot be polymorphic once it is instantiated.

But it can! You mix types and classes which forces you to a very complex,
yet limited types system.

Not in an OOPL. Once the pointer is bound to a type through instantiation you cannot reassign it to another type. That constraint is what allows the language to hide the indirection. Without that constraint one would also have to explicitly specify what type T* is currently pointing to (S, U, V, etc.) in every invocation context. That would make the indirection an explicit two-stage affair. Instead of

(1) get referenced identity (T*)
(2) access referenced target interface (T)

one would have

(1) get referenced identity (T*)
(2) get actual type interface (S)
(3) access referenced target interface (S)


But this is exactly how polymorphic pointers work! 2-3 is dispatch on a
polymorphic pointer. And it is how C++ pointers work, they all are
polymorphic, when the target is a class [which is a design flaw].

That is exactly how polymorphic dispatch works _when T is a superclass in a subclassing relationship_. I am talking about the situation where T is not subclassed (i.e., where T* is an ordinary object pointer). If you want to introduce polymorphism by substituting entirely different types (unrelated by subclassing) after pointer instantiation, then you would have to make that mapping explicit in the syntax because you do not have a convenient subclassing tree to define the rules of substitution.

The compiler could still hide the (3) indirection, but not the (2) step.

Not to mention the confusion it would create for the developer when T* is actually pointing to an unrelated S type rather than the original T type in some context. B-) That sort of anarchy puts software developers in padded cells.


Only if you force them to "class = type" illusion. They aren't equivalent.

I don't care whether we are talking about classes or types. When the developer uses a pointer called "T*" and that pointer is actually currently pointing to an S (i.e., it is really an S*), then one has anarchy that is utterly unmanageable.

So pointer object is not an object? This is what I meant. You have to
postulate some things, which are not objects; have operations, which aren't
methods; have types, which may not have classes; related to each other, but
in some magic way. I don't want a language like this.

No, a pointer is not an object in an OO sense. It does not abstract an identifiable problem space entity.


Really? Let I write a C compiler. In that case pointers are definitely in
the problem space.

Of course. But it won't be abstracted the same way it is in the language meta-model. That's because defining a language and building a compiler are still different problems even though they share the problem space. (Though it will likely be close because of the shared problem space.)

Anyway, I wouldn't even call a language OO, if it has pointers, but those
aren't objects.

I don't know what I would call a language where pointers were defined as first class objects. An OODBMSL? That would be analogous to making functions first class objects. Then I would know what to call it -- an FPL. B-)

You don't allow aggregates of values?

Actually no (except in private object implementations at OOP time).


How can you create a container of values? Pointer values shouldn't be
aggregated as well, I presume.

You can only aggregate objects (via relationships). In practice, that means aggregating object references in collection classes at OOP time. Values are always represented as scalar ADTs in the OOA/D. The ADT implementation may provide an aggregate of values, but that is encapsulated. It is only when gets down in the muck of 3GLs that one must map ADTs to data stores. [And where the 3GL type systems make even more compromises with the hardware computational models. B-)]

So an aggregate containing a pointer to T is itself not a pointer. See, you
have to introduce a third class of things: values, pointers, and aggregates
of pointers. Where it will end?

<snip>

So there are several "classes of things" that are relevant to the OO paradigm. In fact, I would add objects, properties (subclassed to values and operations), and relationships to the list of fundamentally different sorts of things that coexist in an OO context. I could even make a case for message.

What's the problem with that? Sorting things out by flavor is a common technique for complexity management.


The problem is that it looks utterly complex and without any obvious need.
I doubt anybody would be able to formalize it. Even from "translationist"
point of view, how can you be sure in what you have translated?

And you are using different flavors of pointers for superclasses and leaf subclasses in Ada?!? B-))

The need lies in managing complexity. Divide-and-conquer is SOP.

As far as validation is concerned... How does one know one's 3GL compiler has translated the right thing? At one level one depends upon the determinism of the computing space. That is, one depends on the mapping being well-defined and reusable. At another level, one runs tests. Translationists routinely run exactly the same test suite for functional requirements against both the OOA models and the final executable; just the test harness changes.


*************
There is nothing wrong with me that could
not be cured by a capful of Drano.

H. S. Lahman
hsl@xxxxxxxxxxxxxxxxx
Pathfinder Solutions -- Put MDA to Work
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
Pathfinder is hiring: http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH



.



Relevant Pages

  • Pointers and Teaching C++ [was Re: A little disappointed]
    ... like Java where *many* facilities such as raw pointer access / manipulation ... are removed in order to create a 'safer' language and stop programmers ... then it is probably wiser to teach programming using ... pointer access / manipulation is not a 'high level language'. ...
    (alt.comp.lang.learn.c-cpp)
  • Re: The Decline of C/C++, the rise of X
    ... > tend to be most useful for encoding Boolean values in data structures ... to freea pointer obtained from the gc, ... >> to be a useful attribute of a language. ... Template arguments are evaluated in the scope of the point of instantiation, ...
    (comp.programming)
  • Re: Obstacles for Tcl/Tk commercial application development ?
    ... And once I put an integer into a variable in Tcl, it stays an integer until I assign something else to that variable. ... Usually, when I code, I know the language well enough to know what types the expressions return, so I don't wind up with the wrong types in variables. ... It takes a char* as the second argument, not a pointer to the structure you're trying to write out. ... If I expect my code to pass me an open file handle, and I pass that argument to and it throws, I'm going to catch that error at the top level, log the stack trace back, clean up, and restart the processing. ...
    (comp.lang.tcl)
  • Re: identity...... Was: The wisdom of the object mentors
    ... yields Object pointer is identity. ... typeof is identity. ... Consider container iterator. ... But that would imply nothing in the language. ...
    (comp.object)
  • Re: TADOConnection.OpenSchema documentation???
    ... An object is a dynamic instance of a class. ... dynamically, on the heap, so an object reference is like a pointer (but ... Delphi does not have any automatic garbage collection (but see ...
    (borland.public.delphi.database.ado)

Quantcast