Re: Question on LSP
- From: "H. S. Lahman" <h.lahman@xxxxxxxxxxx>
- Date: Mon, 01 May 2006 16:35:34 GMT
Responding to Kazakov...
However, I think that is beside the point. The issue here is whether ClassY* is related to ClassY via subclassing or subtyping.
OK. But I don't want to distinguish subclassing and subtyping.
But they are very different! Class systems are about set membership and identity while type systems are about property access signatures.
My assertion is that in an OO context they are not even remotely related by semantics. IOW, it would be an apples and photons relationship. So if one does use types to describe both, then those types will not be related via subtyping.
So, are we agree that it is the application domain to decide?
Yes. That was my point later in the message vis a vis LSP.
The semantics of ClassY* is an identity reference and that semantics is exactly the same no matter what class the object being identified belongs to.
Then you should be able to express this semantics in some type-independent
way. I doubt anybody can. OK, let you did it. Then this semantics is still
not about types. As such it is irrelevant to subtyping issue. What is left?
That I still can call methods through pointers. That's subtyping to me,
I don't have a problem with defining ClassY* as a type at the 3GL level. I just have a problem with saying that type is semantically related to the type of an object in the ClassY set. It is only semantically related to types that describe references (e.g., in C++ '&' vs. '*').
Yes. It describes referential semantics. That's a subtype. When it
describes an identity semantics, which wasn't inherited from the target
type, then it is not a subtype (in this part.) I don't see any
contradiction here, because to me there is no full subtypes, they would be
useless. Each new type is both a subtype and not, depending on which part
of semantics is considered.
I still see a contradiction. For either subclassing or subtyping there must be a semantic link (e.g., shared properties) that is intrinsic to both abstractions. A reference is not an object. An object is not a reference. So one cannot make ClassY a subtype of ClassY* or vice versa.
Calling methods through pointers is subtyping?!? Sorry, that notion is so alien to me that I don't even know where to push back. B-)
It looks like substitution, it works like substitution... (:-))
A pointer always gives one exactly the same thing every time it is invoked so there is no substitution. That's true even if the 'thing' is an object that is accessed through the root superclass or supertype. The polymorphic dispatch only comes into play through which specific object is assigned to the pointer. But once it is assigned I see no substitution when navigating the pointer.
To me subtyping is about organizing properties that are shared into groups and organizing properties that are not shared into other groups. In the end subtypes simply map to subsets and the tree is just a Venn Diagram that conveniently allows the distinguishing characteristic of each set, itself, to be a set (of enumerated properties).
Yes.
But my problem is that I don't see that as having anything to do with invoking methods or navigating relationships via pointers.
No problem again. It is not a LSP subtype in in-methods, clearly null
cannot be passed in there. But I don't care, I have generalized ClassY
exactly for that reason, to have a new value: null. Otherwise, I would use
pointers without null, some languages support them.
But that value is incompatible with the "value" (identity) of a member of the ClassY set. In an OO context object identity exists in the problem space and it is necessarily unique to an individual entity. NULL is not the identity of an entity; it is a negative definition -- the value of there being no entity.
= it is not substitutable. So what? It was designed this way. You stick to
absolute substitutability, but there is no such thing in real programs.
I don't think substitutability is relevant here. A Class defines a suite of responsibilities that all member objects /must/ share. It has no meaning for an object without exactly those properties. NULL defines a null set of properties which is a direct contradiction of the class definition. So that can't be substitutable semantically.
The only substitutability lies in the nature of /references/ because a reference's "value" can be defined to include a null set as well as a class reference set. But that isn't really substitutability because the reference isn't an object. It is just a set definition around the reference's data domain, which is quite different than the target object's responsibility domain.
Again no problem. If there is some common ancestor ANY, then why not to
have ANY*? However I don't understand why there should a common ancestor
and why any object needs identify. These do not follow from the problem
space. So it should not be a universally applied relationship. But this is
a debate for another day.
What is the common ancestor between a reference and the object it references?
The object, it is a parallel hierarchy:
ANY <-- ClassY <--- ClassY*
ANY <-- ANY* <--- ClassY*
I think the tree limbs are:
ANY <-- ClassY <-- Y
ANY* <-- ClassY*
Even if you argue that they are all types of some sort and ANY is the Mother Of Types, then the only point of common ancestry is ANY, which has no semantic utility in OO software design. IOW, there is no practical use for ANY in that situation because it cannot have any problem space semantics or the tree will inevitably have LSP problems.
Yes, this is an argument against dynamically typed languages and the idea
typeless pointers. But technically it is possible to have.
ANY <-- Class <-- ClassY <-- Y
ANY <-- Reference <-- Object Reference <-- ClassY*
Yes. This is another parallel hierarchy: each specific type is a subtype of
its class.
Apropos of the point below about our having different viewpoints, the notion of ANY as a Mother Of Types only has academic interest for the design of languages and whatnot. [The notion is analogous to kludges like CObject in MFC. One only needs such things explicitly in an application because of language deficiencies (e.g., to use dynamic_cast in C++) or because a library designer needs a place to implement language-specific stuff (e.g., new) behind the scenes. IOW, one should never, ever have to refer explicitly to CObject or CObect* in application code.]
I agree, "everything is object" is rubbish. A type without methods is
useless. But there are cases where ANY might have methods. I mean things,
which are methods, but usually aren't considered as ones. It is
1. identity
2. copying
3. other factories
4. things like sizeof()
Identity I might concede, but that is a knowledge responsibility, not a behavior responsibility. Since inclusion polymorphism and LSP is primarily about behavior substitution I don't think that counts. B-) In addition, object identity does not have to be explicitly defined as an attribute because of the way OOA/D handles sets and their relationships (i.e., identity is implicit in the way relationships are instantiated). Finally, identity at the ANY level would have to be so vague as to be semantically useless (e.g., how to account for compound identifiers).
As for the other three, I argue that one will be able to find a context somewhere where the semantic definition at the ANY level will have an LSP violation. In fact, trying to get commercial libraries to work together for those things is already a substantial headache.
However, that triggers the thought that you are arguing here on some basis other than OO development (e.g., linguistics as applied to 3GL grammars). My position is that in the context of OO abstraction it makes no <methodological> sense for an identity reference to have a common ancestor with an object abstracted from the problem space.
This puzzles me. Maybe, the source of disagreement is that you think of it,
as being parametrically polymorphic:
ANY <--- ClassY
Ref <ANY>
Ref <ClassY>
But all forms of polymorphism are equal to me.
No, I wasn't thinking of parametric type substitution (or even the more general OOA/D parametric polymorphism). I think it is more fundamental. I see the issues here as being about entity identity and problem space abstraction. I have the impression you are viewing it from a pure 3GL/type system/language design perspective rather than OO software design.
Sure. You know that I don't believe in nGL. (:-)) I view it is a pure
decomposition into types. Problem space phenomena are mapped to values.
Commonalities are to types. Meta laws are refactored as sets of types.
Then how do you account for the fact that OOA/D does not even use types? B-)
The mechanics of message passing are a pure OOP issue. In OOA/D one does not even care if behaviors are represented by procedures (i.e., the notion of 'method' is much more abstract than at the 3GL level). Thus writing to a hardware register is represented exactly the same way in OOA/D as invoking a procedure. IOW, in OOA/D the access mechanisms of responsibilities have been completely abstracted away so type systems are irrelevant.
For example, I don't think LSP is about the mechanics of types, so talking about details like value vs. object or NULL is really beside the point. Liskov simply used types as a formalism for expressing the notion of constraints on substitutability.
But I agree with that.
In my view one cannot even begin to talk about LSP _in practice_ unless one has a specific problem space in mind. It seems axiomatic to me that one cannot design a nontrivial subtype tree without LSP conflicts for all possible contexts of access of the root subtype (i.e., one would have the ANY problem above if the root had any significant "common" semantics). [Note that the only reason libraries like MFC work with CObject providing operations like new is because the context is already narrowly constrained to a particular language design. Even then, they break when one brings in another commercial library from a different vendor!]
IOW, LSP is not practically achievable in the general case, but it has great value when it is achieved in specific situations. So in a software development context one needs to have a specific problem space in mind.
Again, I agree. I also agree with your point about 3GL. nGL forces you to
refrain from considering substitutability as an abstract problem. It is
true that if everything is OK, that will be automatically substitutable.
(We believe that God created our world in accordance with LSP. (:-)) But
how can you tell if your design is OK? To me the substitutability problem
is has a value of its own. I want to be able to handle it at the level of
3GL, without inspecting the problem space. The problem is the power of 3GL.
It seems that LSP is inconsistent with this power. I am not ready to
sacrifice it by going to nGL, even for the sake of LSP conformity.
So our real differences lie in the vagaries of 3GL type systems, which I argue are not relevant at the 4GL level -- precisely because they have been abstracted away by decoupling message from behavior. But you don't buy the notion of a 4GL because of a Catch-22: you can't extend your type systems to that level of abstraction, so that level of abstraction can't exist. B-) You need to get out of the type system mud and smell the abstractions. B-))
*************
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
(888)OOA-PATH
.
- Follow-Ups:
- Re: Question on LSP
- From: Dmitry A. Kazakov
- Re: Question on LSP
- References:
- Re: Question on LSP
- From: Dmitry A. Kazakov
- Re: Question on LSP
- Prev by Date: UML Component templates
- Next by Date: Re: Managing multiple instances
- Previous by thread: Re: Question on LSP
- Next by thread: Re: Question on LSP
- Index(es):
Relevant Pages
|