Re: Question on LSP
- From: "H. S. Lahman" <h.lahman@xxxxxxxxxxx>
- Date: Fri, 05 May 2006 19:26:13 GMT
Responding to Kazakov...
But is-a in the problem space is not is-a in the programming language. It
is very dangerous to mix them. I can model problem space's is-a by many
ways, and many of them will not be is-a. In my view, subtyping expresses
nothing. It is a relation which can be evaluated to true or false. Same as
an integer number, which also expresses nothing. We can use either to model
something that in our opinion has a similar structure / behavior / shape /
properties. I stop, where you begin.
I don't how to avoid "mixing" them. OOA/D abstracts from various problem spaces. The role of OOPLs is to provide an implementation for the OOA/D design that is closer to the hardware computational models. To preserve correctness, I think that implementation must preserve the OOA/D semantics.
I think it would be a wrong idea. The semantics is different, and proving
correctness becomes a nightmare because of that. This is why generated code
is much easier to prove correct, by construction.
Not really. Translationists routinely execute the same test suite for functional requirements against the OOA model that they run against the executable. All that changes is the test harness.
At another level, it seems to me that removing problem space semantics in the transformation would eliminate any possibility of requirements traceability.
It seems to me that failing to preserve is-a semantics in the type systems would be much worse than choosing OOPL names like X.Y() for OOA/D abstractions like Account.withdrawal().
It is not failing, it is expressing it in a different way. When you have
virtual memory management, do you really care about physical addresses?
No. But I care very much about instantiation and two instances with the same identity are a no-no.
Instantiation of types in an OO is-a context is inherently different that instantiation of types in, say, and RDB context. In OOA/D referential integrity is married to set membership and that, in turn, is married to problem space semantics. If one changes the nature of the is-a relationship in the transformation of OOA/D -> OOP, how can one guarantee unambiguous mapping for things like enforcing referential integrity?
But I can't help pointing out that nobody had much interest in type theory in general and LSP in particular until OOA/D became prominent. When the only abstractions one needed to play with were procedures, one could make do with less sophisticated graph and set theory for 3GL language design. B-) So I find it hard to believe that the type mavens weren't strongly influenced by OOA/D abstraction semantics like is-a.
It could be true or just a coincidence. Clearly we have a difficult problem
of growing complexity at hand. Old tools and customs stop working. So
people are searching in all corners. My hope is in a great unity, which
would cooptate functional and relational guys.
I don't see that level of cooperation happening real soon. B-) The OO, functional, and P/R construction paradigms are fundamentally incompatible because they have different goals and objectives. The best one can do is provide a notation that is sufficiently bland that all can use it. But the way they use the notation when they build software will remain methodologically quite different. 3GL type systems provide a sufficiently bland semantics to design specialized languages for each paradigm, but those languages will remain specialized. Nor can one talk about things like LSP unless one defines the problem space AND the construction methodology.
LSP is only an issue for polymorphic dispatch. In fact, of all the forms of polymorphism available to OO development, LSP is only relevant to inclusion polymorphism. And inclusion polymorphism does not exist without and is-a semantics.
It does not, it has a potential to become. Is-a is abstracted as "is
substitutable for" and the latter is as "inherits this method from."
Another fundamental disagreement. "is-a" does not imply anything at all about substitutability. Is-a is abstracted as "is a member of that set". Nothing more.
I am constructivist, show me the set! (:-))
The set is any set in the tree. The is-a relationship is just a Venn Diagram in tree form; nothing more. It is in tree form because that makes it convenient to deal with the fact that each set is defined in terms of another set (of properties).
Substitutability only comes into play through polymorphic dispatch when a member of a set is accessed during collaboration and then only if the access is through a superclass. And inheritance is orthogonal to subclassing; inheritance merely provides a suite of rules for resolving the properties of a particular member of a root superclass. IOW, inclusion polymorphism and inheritance are enabled by subclassing, but they are quite different things than the is-a semantics.
This is our disagreement. I don't consider values, they are only postulated
to exist. Our only interest in them is how values are bound by methods
(arcs). If there exists a path, it is a subtype relation. I don't care
where that transition leads, there is one, that's all.
Wow. Again, that is so far from my ken that I don't know how to respond. I have no idea what graph you are talking about or what methods have to do with the discussion.
OK, if you mean a possibility to instantiate values of S as ones of T. Why
T* cannot be instantiated as T?
I mean that the [Strategy] is-a exists with or without the R1 relationship and is unaffected by whether R1 exists. But LSP is not relevant unless the R1 relationship exists.
I would say LSP is relevant to any relationship.
1 grades *
[Teacher] -------------- [Student]
All members of the [Student] set have identical properties, including identical behaviors. Same thing for all members of the [Teacher] set. No matter what collaborations take place over that relationship, the same behaviors are involved, regardless of individual member identity (i.e., which instances actually participate in the collaboration). In transforming to 3GL code, each class would be mapped to a single type. Thus there is no behavior or type substitution in any conceivable collaboration. So how can LSP be relevant?
Nor does it matter how one implements the relationship (T vs T*).
(Only if the implementation is conform to LSP.)
The implementation of the relationship does not matter, which is my point. I can implement the relationship with several different mechanisms at the 3GL level. But that is all they are: implementation mechanisms. The pointers are mechanisms that exist at a different level of abstraction from LSP. Pointers have nothing to do with LSP and their use is completely independent of LSP. The LSP compliance die was cast when the relationship was defined, not its low level implementation.
But I've been here already so I suggest we give up on the whole pointer thing. We are just going in circles.
The if-a-tree-falls-in-the-woods-is-there-a-sound? argument about the possibility of substitution doesn't matter. It is the existence of the /relationship/ that makes LSP substitutability concrete, not its implementation via pointers or any other mechanism.
That's OK, but it is already beyond types. I want (and I think Liskov did
too) to judge about types regardless to what they implement.
I agree it is beyond the semantics of pure types. But it is not beyond the semantics of substitution of types. IMO, Liskov's original question (which Daniel T. quoted) was a philosophical question about what one /does/ with types. She asked, "What does it mean...". That question seems to me to be about the context semantics rather than the type semantics.
[...]
What I disagree with is that pointer dispatch is the same as polymorphic dispatch. The pointer dispatch is not /intrinsically/ polymorphic; any substitution is purely in the value of the pointer. The pointer dispatch semantics are completely and unambiguously describable without any mention of polymorphic dispatch. But one cannot describe LSP substitutability without talking about polymorphic dispatch and, in an OO context, is-a relationships.
Hmm, I could have fat pointers and thin objects. This is actually what
always wished to see in modern OOPL. Technically it means that the type tag
is not stored in the object (that will have no type identity). It is in the
pointer. Note that semantically fat-pointer-to-thin-value is equivalent to
thin-pointer-to-fat-value. Both dispatch, both are dynamically polymorphic.
But it breaks your "is-a" fiction. BTW, having this mechanism, you will be
able to translate your OOA/D "is-a" much more uniformly and efficiently.
I think all you are doing is moving the interface definition (type) out of the object into into the message addressing mechanism. Note that UML already recognizes that interfaces are separate model elements from classes, so that decoupling, combined with separation of message and method, is already fully abstracted at the 4GL level.
As an analogy consider that I can describe any complex behavior with interacting state machines using asynchronous event-based communications. Almost always the state machine actions will be executed at some lower level by making synchronous procedure calls. Does that make a synchronous procedure call inherently asynchronous? No. Like a pointer, it is just an implementation artifact. In this case that call is /inherently/ synchronous even though it implements an asynchronous solution. So the synchronous procedure call has no more to do with asynchronous behavior than a pointer has to do with polymorphic dispatch.
Agree, but why do you think that this supports your point of view and not
mine?! (:-))
LSP can only exist if there is type substitution. One can only have type substitution if there is polymorphic dispatch. Pointers can implement any sort of dispatch. IOW, pointers don't care about the semantics of the dispatch mode, so they aren't relevant to LSP.
[Let's review the definition of polymorphic dispatch for this context. It is substitution of different properties accessed by some client in a particular collaboration. That substitution is constrained such that the client can be indifferent to the substitution. Corollary: one cannot substitute different properties unless one provides different types (3GL type systems) or objects from different classes (OOA/D class systems) as participants in the given collaboration.]
After all you can always add Dereference_Error exception to the target type
contract and everything will be perfectly substitutable. (:-))
Not in a well-formed OOA/D. B-)) That would lead to a cohesion problem for the target abstraction. Whatever the problem space entity underlying the target abstraction has as intrinsic characteristics, they surely doesn't include exception processing.
Ah, that nGL again! (:-)) Exception processing is not necessary a model. It
could serve for "phase transition." You might be unable to have one model
working well in all cases, just because the solution space has a structure
of its own. You have to handle "bifurcations" by changing models. (Not
everything is a model. I am *not* a translationist, as you know! (:-))
Whether one explicitly models exception processing depends on the problem space. If there are rules and policies around exceptions that are unique to the problem in hand, one would have to model it explicitly in the solution. OTOH, if it is some artifact of the computing space with well defined and has standardized infrastructure, one would leave it to OOD or OOP.
What I am saying here is that IF one explicitly models it at the OOA level, then one would still have to abstract its responsibilities separately from those of the target. That is, the exception would be abstracted separately in some other object(s) than the target in your example.
Yes, but this is a different case. I meant exceptions used as artefacts of
design. Dereference_Error models nothing, but a design weakness. But it is
OK, because there might be no better design.
If you are modeling an operating system or most hardware drivers, the notion of an Exception as an identifiable problem space entity would likely be correct modeling.
However, I was thinking of knowledge attributes in general. On thinking about it, I have to retract this particular statement because knowledge attributes can be the root of LSP problems. I have seen Shape trees where the real problem was that completely different knowledge <private> attributes were defined for subclasses and superclass for the same semantics:
Rectangle:
majorSide.
minorSide.
Square:
side.
This is just asking for trouble with the behavior substitutability because the implementations that the behaviors assume are incompatible.
I don't see it as the problem. Both are just implementations, they could be
any. The problem is in what they model, in the problem space. Circle is not
substitutable for ellipse already there.
Really?
[Shape]
A
|
+------+-------+
| |
[Rectangle] [Square]
- majorSide - side
- minorSide
Try defining a constructor for [Shape] that any client can use. Or
[Rectangle]
+ majorSide
+ minorSide
+ area ()
A
|
+-------+--------+
| |
[Square] [Rhomboid]
- side
Presumably Square.area() uses Square.side for the computation. Where does the value of Square.side come from? Even for this simple case all the answers are ugly. For more complex situations it would get worse.
But the real problem is the redundancy. For all [Square] objects you will also have to put the value of Square.side into both majorSide and MinorSide attributes so that clients of [Rectangle] can get the right stuff when they ask about sides.
I don't have your "is-a" problem, so Rectangle and Square can have
different representations. majorSide is a member interface. It is not a
member. Square can implement that interface in the in-mode (getter.) It
cannot implement it in the out-mode (setter.) So it becomes an incomplete
(partial) subtype of Rectangle. Technically, subsets remain, but they are
moved to methods. It might look a small difference, but it is important,
because using your language, differently to attributes methods need not to
be always instantiated. So, in any context where majorSide setter is not
called its absence represents no problem.
As above, you are using a terminology here that seems to have very strange semantics (e.g., 'member'). For one thing you seem to be equating interfaces to methods to the point where you artificially create one (getter/setter) when needed to access knowledge properties. But knowledge responsibilities are not behavior responsibilities so one does not need a procedure call to access them (though getters/setters can be a good practice for other reasons). So using terms like 'method' to describe both is confusing.
IOW, we are still on different planets.
*************
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
- From: H. S. Lahman
- Re: Question on LSP
- From: Dmitry A. Kazakov
- Re: Question on LSP
- From: H. S. Lahman
- Re: Question on LSP
- From: Dmitry A. Kazakov
- Re: Question on LSP
- From: H. S. Lahman
- Re: Question on LSP
- From: Dmitry A. Kazakov
- Re: Question on LSP
- Prev by Date: Re: Searching OO Associations with RDBMS Persistence Models
- Next by Date: Re: MVC: Internationalize Controller?
- Previous by thread: Re: Question on LSP
- Next by thread: Re: Question on LSP
- Index(es):
Relevant Pages
|