Re: Question on LSP



On Thu, 11 May 2006 17:51:52 GMT, H. S. Lahman wrote:

Responding to Kazakov...

It is far from trivial. There is aliasing problem. There is a problem of
stored addresses. There is a problem of same thing having different
equivalent addresses in the same context. There is a problem of addresses
depending on the context etc.

Been there; done that. It is not a big deal if you know from the outset
that the addresses will move. Since most object addresses in an
application don't move, one only has to provide encapsulation and
infrastructure for those that do. (Hint: one manages it the same way
the OS virtual memory manager does it -- indirection through a table
lookup.)

[Some of that encapsulation one gets for free in a well-formed OO
application because objects are /never/ directly visible outside the
subsystem they live in. So identity mapping to addresses in distributed
contexts is always managed in the interface through table lookups of
handles anyway.]

So in effect, you have thrown away these machine addesses by replacing them
with some type implemented by the LUT. This is exactly what I meant.
Identity is not God-given and the machine address is its prohet. No.
Identity is just a property as any other, and to implement it you can use
any type you find appropriate.

[...]
Why construction paradigms cannot be equivalent? In the sense that you were
be able to impartially judge about their applicability in each concrete
case?

Because construction paradigms have different goals. If all we wanted
to do was write compact programs quickly and intuitively, we would all
be doing functional programming. If our dominant problem is converting
an RDB view to a UI view and vice versa, we would all be doing P/R. But
OO development has a quite different goal: long term maintainability in
the face of volatile requirements for large applications. Different
goals require different construction practices.

Huh, it is like to say that different destinations require different
construction of trains. In Europe it was definitely (and partially remains)
so until all countries agreed on one width of the track. Obstacles are
political, not technical.

Well, clearly, pure interface substitutability has less problems. But there
are important cases where T is not just an interface.

But that's all a type is: an interface to a specific set of properties.

+ an implementation of.

Types basically exist because all 3GLs use procedural message passing
by definition.

I beg to disagree. I think types exist because they have to in any more or
less complex logical framework. In that sense 3GL types are
Russell-Whitehead's types.

The type just defines the signatures of a specific set
of procedures. (An ADT is just a special case where access of data is
cast to procedure getters/setters.) One only has polymorphic dispatch
if different objects having different procedures can be accessed using
the same procedure signatures (i.e., through the same type).

It is a somewhat "pattern matching" wording (flawed, IMO), but in essense
it is correct. To dispatch one needs exactly one type, that's is a class.

Types [T1] and [T2] are not substitutable for any of the specialized
properties of [T1] and [T2] under any circumstances. However, they are
<supposed to be> substitutable for the properties defined by [T]. There
are two access possibilities:

Yes, but again there are cases where the graph above has cycles. I.e. when
you might want to use T1 and T2 interchangeable:

[T1] <------> [T2]

But only for the properties defined by [T].

Also, I'm afraid I don't see any graph cycle here.

I don't have your nice tool for drawing 2D graphs in ASCII. OK I'll try. If
you want an equivalence between three types there are many possibilities.
Ones is:

[T]
A A
/ \
V V
[T1] [T2]

Subtyping is transitive. So because T1 is a subtype of T it is also one of
T2, because T2 is a supertype of T. And in reverse, T2 is a subtype of T1.
So the path:

[T1] <------> [T2]

[...]
Peer-to-peer collaboration works for any problem and can be transformed
to any implementation environment. Things like tripartite messaging
don't. More to the point, broadcasting can always be emulated with
multiple peer-to-peer messages so one doesn't need a special construct
for those situations where it occurs.

But it is the same agrument RM people are using to justifiy limitations of
their approach. You can prove 1+1=2 in 120 pages proof starting from ZF set
axioms. The question is why youi should do that? Integer might be a set,
but it would be awful to deal with integers in such representation. So the
peer-to-peer collaboration might be complete (though I reserve doubts about
it), but that does not make it KISS. I think double dispatch is quite
natural and intuitive concept. If not then the whole idea of dispatch (and
so polymorphism) was rubbish.

BTW, a litmus test for any theory, starting since Cantor times is - how do
you construct numbers in your paradigm? (RM people are of course unable to
answer this in any reasonable way.) What about your model? Let you have Z
and Q. Along which relationship does "+" sent?

In a practical sense it is not relevant at the OOA/D level. Such things
are really only of concern once hardware gets into the picture. So one
needs to narrowly define semantic abstractions at the 3GL level for that
sort of thing to be consistent with the hardware models. [Which
involves compromises, like defining knowledge responsibilities in terms
of memory data stores. That, in turn, leads to language pitfalls for
access decoupling and the whole getter/setter debate.]

At any higher level of abstraction, one just accepts an existing general
model for things like numbers and arithmetic operations. IOW, I don't
need to "construct" them in my OOA/D; I already know what they are.

The song remains the same - how do you know that structures complex like
numbers would never appear in the problem space? If you cannot handle
numbers, why are you sure that you can anything else? (This is one reason
why I count nGLs for lower-level abstraction than 3GLs.)

It knows that T has Foo, this is what allows you to do Ptr.Foo (). Only
typeless pointers know [almost] nothing. But we don't want them at all.

T knows, but not T*. T* is simply an indirection to get to a T.

Note that the language allows us to use a name like 'T' on the reference
as a mnemonic so that the developer can keep track of what is happening
with the indirection. Object* would be perfectly acceptable as an
indirection mechanism in providing correct code. But the code would be
rather unreadable and the compiler would be less able to detect
developer screw-ups. IOW, the 'T' is just syntactic sugar designed to
address the developer's problems of maintainability and fallibility
rather than the correctness of the solution.

I see. Well, it is a very retrograde view, I must say. But let's take it
for a while. It would just mean that you don't have pointer type as proper
type. That effectively closes any discussion about whether a non-type is a
subtype or not. Modern languages have proper pointer types. You can define
new operations on them, you can influence their representation etc. In this
case you cannot wave your hands - it is just a sugar. Because it is not.
Whether you need such things in your model is another question. Note
though, that assuming your sugar to always exist, heavily damages language
performance (prevents some valued optimizations) and plain wrong when you
dealing with things which semantically cannot be referenced (values in I/O
registers, clock readings, random generator realizations).

Retrograde?!?

I didn't say a pointer isn't a type. I just said that its type
semantics has nothing to do with the T type.

Hey, type's semantics is up to developer's discretion!

If it has a type it is
something generic like Object Reference.

BTW, back to the original point, an Object Reference is polymorphic in
the sense that the reference assigned to it can be for any Object
subtype (T, T1, T2, S, whatever). I now suspect that is what you had in
mind when I originally disagreed that pointers are polymorphic.

Yes, that's a polymorphic pointer.

That's the language designer view.

But I am looking at it from the application developer view. In the
context of OOPL semantics it is never polymorphic _once it is
instantiated_. The syntactic sugar of T* is just evidence of the fact
that a given pointer cannot be assigned to an object of any other type
than the one the developer had in mind when instantiating it.

No, that's a different case.

1. The first one is polymorphic pointers. That is when a pointer points to
a class. The target is of either type derived from T. The class is based on
the set of types {T, T1, T2, S ...}. One have to distinguish a pointer to T
(and only T) and a pointer to the class rooted in T. They are of different
types.

2. When methods are accessible via pointer. Your language could allow you
to have polymorphic objects of the class based on the set of types {T, T*,
T**, T***, ...}. On this object, of which you don't know, whether it is T
or T* or T** etc, a call to Foo will dynamically dispatch.

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

IOW, the
polymorphic die can not be cast once one instantiates the Object
Reference. It can be reassigned to different objects of the same type,
but it cannot be reassigned to objects of different types. As a
developer, that construction constraint is absolutely essential to
maintaining my sanity because referential integrity is a problem /after/
pointers are instantiated.

Yes, but it is just a statement about values of the pointer type. A pointer
to integers is constrained to point to integers in exactly same sense as
integer is constrained to be integer. That does not limit either to become
a subtype of any other type.

Foo can only be called on T.

That's Foo 1. Foo 2 can be called only on T*. Foo polymorphic can be called
on any (on one the class).

What?!? We seem to be on different planets again. If T* is pointing to
a T, then the only Foo available is the one on T. T* is just an
indirection mechanism to get to a T. One /never/ gets a different Foo
from accessing through T* than one gets by accessing directly from T.

See above. In my model T* and T are different types. You just cannot call
T::Foo on T*. It is no-no. So you call T*::Foo which is *implemented* as
pointer dereferencing followed by a call T::Foo.

I agree that Foo is not an element of the reference type and I am not
suggesting one is calling Object Reference::Foo. (Note that I said
explicitly that Foo is only available on a T.) However, the constraints
on what the Object Reference type can point to that preclude
polymorphism _once it is instantiated_ allow the language to hide the
indirection. Again, the language is just providing convenient
syntactic sugar to allow a Client to access a T::Foo indirectly through
a reference.

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

If one could, one has Anarchy rather than an OOPL. B-)

One could! (:-)) You can define T*::Foo so that it would call T::Bar
instead. In any case, a reasonable implementation of T*::Foo would throw an
exception if the pointer is invalid.

Here I am referring to the notion that T* is inherently polymorphic.
Once it is instantiated it can't be or else one has the same sort of
anarchy as FORTRAN's assigned goto.

(Maybe assigned goto was the first example of dispatching calls! (:-))

That is the polymorphism. You don't know what it does. After all, T::Bar
could marshal T to another computer, call T::Foo there and marshal the
result back. But if that fulfills the contract, why do you care?

When the client invokes Foo, it is always from T, regardless indirection
or what language implementation mechanism is used for addressing a T.
That's why modern OOPLs don't make a distinction and always address
T.Foo. The fact that the language always introduces a hidden T* is a
pure language implementation issue.

Maybe, but I don't want implicit assumptions. If T* is a side effect, then
one cannot talk about identity. If it is an intentional choice to achieve
identity, then that should be made explicitly.

Well, personally I much prefer the always-a-reference approach. It
leads to a lot less foot-shooting.

But it is fundamentally inconsistent with always-an-object approach. You
need things which aren't references. Reference itself is not reference. You
will lack too much reflection if you would try to pursue this approach it
its logical ends.

I don't see an inconsistency. [In fact, I don't know what the
always-an-object approach is. B-)] The reference is always assigned to
exactly one object so referential integrity is unambiguous about
identity. I also don't see why one needs problem space object embedded
in the implementations of other problem space objects. (For computing
space brickabrack like Array and String that the OOPLs provide, it is a
different story because those things describe the implementation of the
object.)

As a practical matter languages like Java that use only references seem
to be a lot easier ot learn and use than languages like C++ that provide
both embedding and references.

I also don't see what reflection has to do with this issue.

Is reference an object? Does this object same that it points to? When you
aggregate references, is the aggregate of referenced objects or target
objects? Is the aggregate itself an object? Is it a refernce? Do you need
objects, references, aggregates of objects, aggreates of references,
references to references, reference to aggregates of references of
aggregates, and so on and so far all distinct entities, hard-wired in the
language?

Either you have to postulate references or to try to reduce them to
objects. I want the latter.

--
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de
.



Relevant Pages

  • Re: Question on LSP
    ... In a dynamically typed language it becomes ... Polymorphism remains because it is a class, no specific type of the class ... is bound to the pointer until run-time. ... Each object references its methods! ...
    (comp.object)
  • Re: A taxonomy of types
    ... It was just a pointer to ... polymorphism. ... Nominal vs. structural typing? ... Yes, the language might appear as if object type would change, but I doubt ...
    (comp.lang.misc)
  • Re: Nested function scope problem
    ... term "binding", and that whatever it means> (I'll have to read more on that, ... (like a pointer to an object's address). ... Isn't "reference" ~ "C ... In a language like C the name doesn't hold anything either. ...
    (comp.lang.python)
  • Re: pass by reference
    ... The only reason this doesn't work as they think, is because assignment ... because the pointer is what val *is*. ... The parameter is a reference, ... language that is entirely pass by value. ...
    (comp.lang.java.programmer)
  • Re: The Java no pointer big fat lie!
    ... A pointer is a memory address in C, no more, no less. ... >>not what a reference in Java is. ... reference types, it wouldn't be a good comparison. ... My statement is correct with reference to the C++ language. ...
    (comp.lang.java.programmer)