Re: Question on LSP



Responding to Kazakov...

Since OO IDs do not have to be explicit attributes (e.g., they can be addresses),

They cannot be addresses, because addresses may change during program
execution. ID has a definite type. One can use for this purpose the type
Machine_Address. But only under some circumstances, namely when
Machine_Address implements the contract of ID. So it is first identity,
then address, in that order, not in reverse.

All the OO paradigm requires is that identity in the problem space be unique. There is no problem with using addresses as identity in the implementation so long as referential integrity is preserved somehow. (Which is primarily an OOP problem.) That will be more difficult if one moves objects, but it is still a routine problem for things like home-grown memory managers.


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

At the OOA/D level identity is abstracted so that identity need not be explicit. However, consistency with address identity is implicit in the way OO relationships are defined and instantiated at the OOA/D level. IOW, the OOA/D relationship paradigm is /designed/ to be readily implemented with addresses because that is the most efficient mechanism for a memory resident application. That's not true for relationship paradigms like those used in RDBs, which are comparatively inefficient when used in memory.

Look at your types that define objects in the OOPLs in a practical application. Where is the object identity? Most of the time the type definition has no mention of it at all. But every object in memory is uniquely identified by its current address in memory and that identity is what is used ubiquitously by references, offset tables, and whatnot.


No, this is the crux. If I don't contract identity, then there is *no* one.
That precisely means that there is no legal way to identify any given
object of this type. They all are indistinguishable. If OOA/D identifies
some objects as having identity, then that must be explicitly specified in
the corresponding type contract using available language gears.\

Again, we are on quite planets different here. In OO applications object identity is rarely explicitly described in classes OR types. Yet every object still has unique problem space identity even at the 3GL level because it has a unique address in memory. Referential integrity has already been managed through defining relationships and message addressing in the OOA/D. All the OOP developer needs to do is make sure that referential integrity still holds when converting the OOA/D specification to an OOPL implementation.

Since OOPLs are supposed to implement OOA/D specifications, the languages already have constructs like address pointers that provide addressing in a manner that is consistent with unique identity as a memory address. Since objects in memory usually don't move, the language can largely hide the identity mapping. So the OOP programmer usually does not have to worry about object identity so long as the OOA/D specification is converted literally.

The OOP developer would only have to worry about explicit mapping to addresses in special contexts like local memory managers or distributed interfaces. In those special situations the OOP programmer will have to provide explicit infrastructure like handles and lookup tables to ensure referential integrity of object identity. Though uncommon, such situations are common enough that standard techniques have been developed to handle referential integrity much like design patterns.

To what extent the difference in methods influences one in languages and
reverse? Isn't it so that one cannot mix methods just because there is no
way do describe them same language? If the goal to have one language per
one construction method, then indeed there is little hope. But why it
should be the goal?

I think it is far more than just the language. It is about the overall construction paradigm.

For example, one can implement a state machine using either a Moore or Mealy model. But the Moore model is much more natural to OO development while the Mealy model is much more natural to procedural or functional development. The models themselves are mathematically equivalent and completely interchangeable but they make a very big difference to the way one /thinks/ about solving the problem in hand.


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.

However, the OO is-a relationship involves multiple distinct types whose property sets are different.

[T]
A
|
+------+------+
| |
[T1] [T2]

Type [T] has some fixed set of properties. Type [T1] has a set of properties that include [T]'s plus some additional specialized properties. Similarly type [T2] has a set of properties that include [T]'s and its own, unique specialized properties. Objects will /always/ be instantiated as either a [T1] or a [T2].

[Languages like C++ that allow instantiation of just a [T] object are broken. One inevitably gets in trouble in during maintenance when subclasses are added because referential integrity is ambiguous; some members of [T] are defined as negatives (i.e., NOT members of the existing subsets). Note that such implications are not so obvious in a type system, which is why we have broken OOPLs. That's one reason why it is important to get the OOA/D right in terms of class sets before going to OOP's type interfaces.]


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. Types basically exist because all 3GLs use procedural message passing by definition. 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).

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.

However, I don't see it as a types relationship either. That's kind of my point about OOA/D not using type systems. B-) However, to implement it at the 3GL level one will have to deal with a type when mapping collaboration messages. But there will still be no type substitution involved because any Teacher always accesses any Student through a single Student type (and vice versa).


Yes, it is not a substitution between Teacher and Student, it is between
Abstract_Referential_Containter and Teacher; Abstract_Referential_Element
and Student. It should be a sort of abstract relational algebra
instantiated in Teacher/Student pair.

You've lost me again because I think you are designing languages rather than solving problems. Surely your 3GL code is not going to have things named Abstract_Referential_Container anywhere in it(?)


No instances of. But the type itself (Abstract_Referential_Container) could
be a thing. At least its identity (type tag) is a thing in practically any
language.

Exactly my point. You are talking about the semantic meta model of the a specific language's type system. I am looking at things from the developer's viewpoint of solving a problem with the language's /implemented/ type system. The Abstract_Referential_Container will never appear explicitly in that developer's code as a type for some solution artifact. It will only be present implicitly in the form of something like a pointer or a problem space superclass.

My issue here is that whatever semantic meta model the language uses, there must be some way for the developer to unambiguously express the OOA/D is-a semantics for some problem space entity. If subtyping is used for that OOPL expression, then the language meta model for subtyping needs to properly enforce the the same semantics. If the language semantic meta model doesn't do that properly, one gets things like C++'s dynamic_cast and instantiation of superclass objects without specifying a leaf subclass.

BTW, it is a message sent to the pair (Teacher, Student).

You have lost me again here. I think you must be designing OOPLs again. B-)


Very much so! (:-))

I knew that!

Relationships can be bidirectional but individual collaborations (messages) among participating objects are always one-way. That is, a Teacher can send a Student a message and a Student can send a Teacher a message over the same relationship, but there aren't any messages to both. OOA/D collaboration is always peer-to-peer (i.e., the 'to' is literal).


It is a ve-e-ry restrictive view. This way you indeed will not be able to
get RM guys on board.

It is restrictive in the sense that OOA/D needs to provide a complete, precise, and unambiguous solution specification. But one needs to use the same semantic model elements to describe quite different problem solutions, so the semantics needs to be quite general. It also needs to be precise and unambiguous for a wide variety of implementation environments. The only way to get all that to play together is to diligently apply KISS to keeping model element semantics simple and very narrowly defined.

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.


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.

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

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.

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.

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.


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

  • Re: Question on LSP
    ... not have to be explicit attributes, ... the corresponding type contract using available language gears. ... single Student type. ... Reference itself is not reference. ...
    (comp.object)
  • Re: c# features
    ... I don't really think it's strictly necessary to add return by reference. ... introducing a "return by reference" semantic would not be in keeping with the existing semantics in C# and would unnecessarily complicate the language. ... You obviously disagree, as does the OP. ...
    (microsoft.public.dotnet.languages.csharp)
  • Re: Latest on Windoze Navy software
    ... > If you use a non-portable extension then the behavior of a program ... > that uses that extension is an implementation issue, not a language ... specific implementations which did not impact the language reference, ... > One should distinguish between the semantics of parameter passing ...
    (comp.os.vms)
  • Re: Struct size
    ... reference to base TWO, so I think you still have some work left. ... The C language may have used the notion of 2 in implementation of the ... semantics of unsigned shifts, but I didn't. ...
    (comp.lang.c)
  • Re: Using ref
    ... I then made the assumption that if the object wasn't passed by reference, ... Whether the language supports pass by reference or not is defined by ... C doesn't support it (directly - you can emulate it ... explicit reference syntax." ...
    (microsoft.public.dotnet.languages.csharp)