From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on polar.synack.me X-Spam-Level: X-Spam-Status: No, score=-0.3 required=5.0 tests=BAYES_00, REPLYTO_WITHOUT_TO_CC autolearn=no autolearn_force=no version=3.4.4 X-Google-Language: ENGLISH,ASCII-7-bit X-Google-Thread: fac41,c52c30d32b866eae X-Google-Attributes: gidfac41,public X-Google-Thread: 1108a1,c52c30d32b866eae X-Google-Attributes: gid1108a1,public X-Google-Thread: 103376,2ea02452876a15e1 X-Google-Attributes: gid103376,public From: donh@syd.csa.com.au (Don Harrison) Subject: Re: Real OO Date: 1996/03/22 Message-ID: X-Deja-AN: 143591844 sender: news@assip.csasyd.oz references: <4id031$cf9@dayuc.dayton.saic.com> organization: CSC Australia reply-to: donh@syd.csa.com.au newsgroups: comp.lang.ada,comp.lang.eiffel,comp.object Date: 1996-03-22T00:00:00+00:00 List-Id: John G. Volan wrote: :Don Harrison, wrote: : :>Norman H. Cohen wrote: :> :>:In article , donh@syd.csa.com.au :>:(Don Harrison) writes: :>: :>:|> One question: Assuming a tagged type is declared in Common_Interface and each of :>:|> the operations uses it as a parameter, is it true that only the operations of :>:|> Common_Interface (and not it's child units) are primitive? :>:|> If that is the case, then it would appear that Ada provides no means of defining :>:|> abstractions that are both inheritable and selectively export operations and :>:|> hinders reusability in this respect. :>: :>:This is a good point. If you want to divide the primitive operations of :>:a tagged type into several interfaces, the procedure is more involved: [Norman's example] :Here's an alternative approach that is subtly -- but significantly -- :different: Make the subprograms in the child packages _classwide_ :operations, and have them _dispatch_ to the private primitives: : : package Abstraction_1 is : type T is tagged null record; : private : procedure Op_1 (X: in out T); -- for Specialty_1 : procedure Op_2 (X: in out T); -- for Specialty_2 : end Abstraction_1; : : : package Abstraction_1.Specialty_1 is : procedure Op_1 (X: in out T'Class); -- classwide operation : pragma Inline (Op_1); : end Abstraction_1.Specialty_1; : : package body Abstraction_1.Specialty_1 is : procedure Op_1 (X: in out T'Class) is : begin : Abstraction_1.Op_1 (X); -- dispatching call : end Op_1; : end Abstraction_1.Specialty_1; : : : -- Abstraction_1.Specialty_1 is designed for the exclusive use of, : -- say, Client_1 (although this is not enforced by the language): : : with Abstraction_1.Specialty_1; : package Client_1 is ... : : -- Client_1 can invoke Abstraction_1.Specialty_1.Op_1 on any object in : -- the type-class rooted at Abstraction_1.T (i.e., any object of type : -- Abstraction_1.T or any type derived from that). : : : package Abstraction_1.Specialty_2 is : procedure Op_2 (X: in out T'Class); -- classwide operation : pragma Inline (Op_2); : end Abstraction_1.Specialty_2; : : package body Abstraction_1.Specialty_2 is : procedure Op_2 (X: in out T'Class) is : begin : Abstraction_1.Op_2 (X); -- dispatching call : end Op_2; : end Abstraction_1.Specialty_2; : : : -- Abstraction_1.Specialty_2 is designed for the exclusive use of, : -- say, Client_2 (although this is not enforced by the language): : : with Abstraction_1.Specialty_2; : package Client_2 is ... : : -- Client_2 can invoke Abstraction_1.Specialty_2.Op_2 on any object in : -- the type-class rooted at Abstraction_1.T (i.e., any object of type : -- Abstraction_1.T or any type derived from that). : : : -- Children of Abstraction_1 can derive new types from Abstraction_1.T : -- and override the private primitives: : : : package Abstraction_1.Abstraction_2 is : type T is new Abstraction_1.T with null record; : private : procedure Op_1 (X: in out T); -- overrides Abstraction_1.Op_1 : procedure Op_2 (X: in out T); -- overrides Abstraction_1.Op_2 : end Abstraction_1.Abstraction_2; : : : package Abstraction_1.Abstraction_3 is : type T is new Abstraction_1.T with null record; : private : procedure Op_1 (X: in out T); -- overrides Abstraction_1.Op_1 : procedure Op_2 (X: in out T); -- overrides Abstraction_1.Op_2 : end Abstraction_1.Abstraction_3; : : :(In fact, I'd say in general that any operation ought to be classwide if :it isn't explicitly a primitive. I think you are right. That's effectively what happens in Eiffel. : IMHO, only under very rare :circumstances should a non-primitive operation restrict a parameter to :accept only a specific root type but not any of its derived types.) : :Don Harrison, responded to Norman Cohen's example: : :>This looks like the approach that was suggested by Dale Stanborough. :>I think he found, however that Op1 and Op2 in the child packages were no longer :>inheritable. : :And why should they _need_ to be "inheritable"? A subprogram does not :need to be a _primitive_ in order to be _reusable_. Indeed, a :"classwide" subprogram is quintessentially reusable -- by definition, :it can be applied to any object of any type in its type-class, whether :that type be the root type or some derived type. They should be inheritable if the language is to offer a simple and consistent mechanism for inheritance/polymorphism. In Eiffel, there is only one species of type - the class - which may be viewed as specific or class-wide depending on the context. This makes modelling considerably easier. Ada complicates matters by forcing an atificial distinction of specific versus classwide depending on usage. Since the formal and actual parameters of an operation may be either specific or classwide, the number of possible combinations is 4 compared with 1 in Eiffel. To my mind, this is 4 times more complicated than is really needed. :IMHO, Ada95's clear :distinction between a specific root type (such as Abstraction_1.T) and :its associated classwide type (such as Abstraction_1.T'Class) makes :this kind of issue a lot easier to understand. Well, I have to disagree. I think it complicates it as argued above. :There's only one thing that you might find "awkward" about this: If you :want to invoke, say, Abstraction_1.Specialty_1.Op_1 on an Abstraction_2.T :object, you can do that, but you still have to name the operation :"Abstraction_1.Specialty_1.Op_1" (unless of course you use a use_clause). :You won't "automagically" get a visible operation called :"Abstraction_2.Op_1" that you can call. Yes, that is awkward. Of course, it would be much tidier to remove 'with' clauses altogether because they are redundant. Information about which modules are imported by another is implcit in the references/calls made by the client module. This is the approach taken in Eiffel. Naturally, the inherited modules must be specified; otherwise you would have to uniquely name every attribute and operation in the system! :But so what? Isn't the premise here that a child package like :Abstraction_1.Specialty_1 represents a special interface designed for :exclusive use by one client (or perhaps a few), such as Client_1? That :client is most likely to be interested in a particular abstraction at a :particular level of your class hierarchy. Presumably, all that Client_1 :cares about is that it's dealing with some object in the class :Abstraction_1.T'Class. Perhaps, but it is less flexible nonetheless. : The root type, Abstraction_1.T, might have :derived types beneath it in the hierarchy, but we're assuming that :Client_1 doesn't care what they are, so long as they all adhere to the :abstraction established for Abstraction_1.T'Class (i.e., Liskov :substitutability is upheld). What's Liskov substitutability? :If Client_1 were instead interested in a different abstraction at a :different level in the type hierarchy, then we simply wouldn't set up :Abstraction_1.Specialty_1 as the exclusive special interface it would :use. We might, perhaps, have Abstraction_2.Specialty_1 instead. : :Don Harrison, writes: : :>Now, what happens when we derive a new type from T? :> :>package New_Parent is :> type New_T is new T with ... :>private :> procedure New_Op1 (X: in out New_T); :> procedure New_Op2 (X: in out New_T); :>end New_Parent; :> :>package New_Parent.Child1 is :> procedure New_Op1 (X: in out New_T); :>end New_Parent.Child1; :> :>package body New_Parent.Child1 is :> procedure New_Op1 (X: in out New_T) renames New_Parent.New_Op1; :>end New_Parent.Child1; :> :>[Similarly, for New_Parent.Child2] :> :>We lose the export status of Op1 and Op2 because it was defined in the :>child packages. It's all very messy. : :It's not clear what you're trying to do here. Is the fact that you're :re-using the names "Child1" and "Child2" significant? Is there :supposed to be a connection between, for instance, Parent.Child1 and :New_Parent.Child1 -- in other words, do you intend them to relate to :the same "specialty"? Yes. Sorry for the confusion. New_Parent.Child1 is intended as a refinement of Parent.Child1. I should have used Op1 instead of New_Op1 and T instead of New_T, as you did in your example. :If that's what you mean, well, of course an Ada95 compiler won't read :any special significance into the similarity of child-names (any more :than a family-tree program would read anything into the fact that two :boys from two completely different families both happened to be named :"John"). Yes, that would be a big ask! : So if you have a client that's interested in a particular :"specialty", but several different types participate in this specialty, :then the client just have to "with" each of the various related child :packages. : :But what's so bad about that? It's bad for the following reasons: 1) Because you have to distribute the definition of the abstraction over multiple modules - quasi-encapsulation. In this case, with only 2 views of the abstraction, you need 6 modules. 1 would have sufficed (assuming a bona fide selective export mechanism and combined interface and implementation); 2 if you insist on separate interface and implementation. 2) The selective export information has to be repeated for descendants of the abstraction rather than being inherited. This goes against on of the fundamental tenets of OO - uniqueness. Do something once and once only. 3) It is indicative of a more basic flaw in the language - the 'everyone sees it or no-one sees it' model of information hiding. In other words, it is a hack (albeit a good one). :This seems to work just fine, especially :if you make the "specialty" operations classwide, as I suggest: : : : with Abstraction_1; : package Abstraction_4 is : type T is new Abstraction_1.T with null record; : private : procedure Op_3 (X: in out T); -- for Specialty_1 : procedure Op_4 (X: in out T); -- for Specialty_2 : end Abstraction_4; : : : package Abstraction_4.Specialty_1 is : procedure Op_3 (X: in out T'Class); -- classwide operation : end Abstraction_4.Specialty_1; : : package Abstraction_4.Specialty_1 is : procedure Op_3 (X: in out T'Class); : begin : Abstraction_4.Op_3 (T); -- dispatching call : end Op_3; : end Abstraction_4.Specialty_1; : : : with Abstraction_1.Specialty_1; : with Abstraction_4.Specialty_1; : procedure Client_1 is : Some_Abstraction_1 : Abstraction_1.T'Class := ... : Some_Abstraction_4 : Abstraction_4.T'Class := ... : begin : ... : Abstraction_1.Specialty_1.Op_1 (Some_Abstraction_1); : ... : Abstraction_4.Specialty_1.Op_3 (Some_Abstraction_4); : ... : Abstraction_1.Specialty_1.Op_1 : (Abstraction_1.T'Class (Some_Abstraction_4)); : -- widening : ... : if Some_Abstraction_1 in Abstraction_4.T'Class then : Abstraction_4.Specialty_1.Op_3 : (Abstraction_4.T'Class (Some_Abstraction_1)); : -- narrowing : end if; : ... : end Client_1; : : : ... similarly for Abstraction_4.Specialty_2.Op_4 and Client_2 ... A good workaround. :This seems methodologically quite sound to me. It clearly shows that :Client_1 depends on aspects of Specialty_1 that are manifested at :different levels of the type hierarchy. Operations related to :Specialty_1 that are appropriate for a wider class of objects are :clearly associated with the package that declares the wider type :(Abstraction_1); while operations that are appropriate only for a :narrower class of objects are clearly associated with the package that :declares the narrower type (Abstraction_4). : :What's so "messy" about that? A fair bit, but it's probably as good as you're going to acheive within the constraints placed on you. :(Well, this whole discussion might be a lot clearer if we had a more :concrete example that exhibited this kind of pattern. Ideas anyone?) Why be concrete when you can be vague? I certainly am ;-). :------------------------------------------------------------------------ :Internet.Usenet.Put_Signature :( Name => "John G. Volan", E_Mail => "John_Volan@dayton.saic.com", : Favorite_Slogan => "Ada95: The *FIRST* International-Standard OOPL", : Humorous_Disclaimer => "These opinions are undefined by SAIC, so" & : "any use would be erroneous ... or is that a bounded error now?" ); :------------------------------------------------------------------------ Don.