* Abstract Operations On A Tagged Record @ 2004-10-30 13:04 Marin David Condic 2004-10-30 14:34 ` Dmitry A. Kazakov 2004-10-30 16:07 ` Nick Roberts 0 siblings, 2 replies; 7+ messages in thread From: Marin David Condic @ 2004-10-30 13:04 UTC (permalink / raw) Can someone clarify my understanding (perhaps again) on abstract operations and the implications as a class heierarchy is being constructed? Specifically, what I want is something like this: A base class has an abstract operation, but I may not yet know the data types of the parameters - or possibly even the number of parameters. Let's say what I want is like this: procedure Some_Op (<TBD Parameter List>) is abstract ; When I get to the child class, what I really need is: procedure Some_Op (Some_Param1 : in Integer; Some_Param2 : in Float) ; For a different child class I may want: procedure Some_Op (Some_Param1 : in Integer; Some_Param2 : in Character; Some_Param3 : in Boolean) ; The base class wants to express the basic necessity of "Some_Op" but it really doesn't know if it will have 1, 2 or N parameters or of what type. My understanding is that if I express that as an abstract operation, when I get to the child class, I'll have to supply a real operation of the same name and same parameter profile. (Or can I use a different parameter profile & satisfy the need for a concrete implementation of the abstract op? My recollection is the compiler whines about it or creates an overloading. Is this correct?) I realize I can make another procedure "Some_Op" with a different parameter list and that overloading will take care of me, but then I didn't really need the abstract procedure in the first place, did I? It becomes unnecessary baggage that you have to fill in just because its there, but you have no intention of using it. Do I understand this correctly? Is there away around it? Keep in mind that what I want to express is the notion in a base class that Operation X needs to be present, but the precise parameters to X will be determined by the child class. I'm interested *specifically* in how this works with an abstract tagged record and the inheritance heierarchy that goes with it, so please don't suggest that I'm going about it all wrong and I really need to do something with a generic, etc... If my understanding is correct and it can't be done then we just trash that notion and go to other techniques, but I want to know how this capability works. Thanks. MDC -- ====================================================================== Marin David Condic I work for: http://www.belcan.com/ My project is: http://www.jsf.mil/NSFrames.htm Send Replies To: m o d c @ a m o g c n i c . r "Power corrupts. Absolute power is kind of neat" -- John Lehman, Secretary of the Navy 1981-1987 ====================================================================== ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: Abstract Operations On A Tagged Record 2004-10-30 13:04 Abstract Operations On A Tagged Record Marin David Condic @ 2004-10-30 14:34 ` Dmitry A. Kazakov 2004-10-30 16:07 ` Nick Roberts 1 sibling, 0 replies; 7+ messages in thread From: Dmitry A. Kazakov @ 2004-10-30 14:34 UTC (permalink / raw) On Sat, 30 Oct 2004 13:04:37 GMT, Marin David Condic wrote: > Can someone clarify my understanding (perhaps again) on abstract > operations and the implications as a class heierarchy is being > constructed? Specifically, what I want is something like this: > > A base class has an abstract operation, but I may not yet know the data > types of the parameters - or possibly even the number of parameters. > Let's say what I want is like this: > > procedure Some_Op (<TBD Parameter List>) is abstract ; The first question is what is the class you are talking about. When class refers a package to be extended by other packages etc, then it is far away from Ada, tagged types and not very clear at all. So I assume that class refers here types, and more specifically a hierarchy of types. If so, then Some_Op should be an operation of that type. > When I get to the child class, what I really need is: > > procedure Some_Op (Some_Param1 : in Integer; Some_Param2 : in Float) ; > > For a different child class I may want: > > procedure Some_Op (Some_Param1 : in Integer; Some_Param2 : in Character; > Some_Param3 : in Boolean) ; > > The base class wants to express the basic necessity of "Some_Op" but it > really doesn't know if it will have 1, 2 or N parameters or of what > type. But it *has* to have a parameter of its own type, the parameter of the class. Some_Op is an operation of the "class". This is how clients become aware of it. They (the clients) are in the class so they have this operation too. There are only two ways to have an operation defined on class: 1. class-wide: Some_Op (Object : in out T'Class); 2. primitive operation: Some_Op (Object : in out T); A primitive operation is defined on the class, but it is implemented "piecewise" by overriding (implementing) its parts in derived types. > My understanding is that if I express that as an abstract > operation, when I get to the child class, I'll have to supply a real > operation of the same name and same parameter profile. (Or can I use a > different parameter profile & satisfy the need for a concrete > implementation of the abstract op? My recollection is the compiler > whines about it or creates an overloading. Is this correct?) Yes, it is the case 2, so it is in fact one operation and thus it has one parameter profile. It is merely syntax sugar and an ability to call an override directly, without dispatch, that makes us believe that it has a "different" profile. A primitive operation is abstract when it is not implemented for some of the class types. It is safe to do so because these types will be then abstract and so will have no instances. But any concrete derived type should implement it for itself (by either overriding or inheriting from the [concrete] base). > I realize I can make another procedure "Some_Op" with a different > parameter list and that overloading will take care of me, but then I > didn't really need the abstract procedure in the first place, did I? It > becomes unnecessary baggage that you have to fill in just because its > there, but you have no intention of using it. Yes, because overloaded operations have nothing in common except the name. The idea of abstract primitive operation is to define an operation on the class. The class is needed only to write programs in terms of that class. One cannot write programs in terms of names without any semantics. For this there would be enough to have a text editor with cut/paste and substitute or a preprocessor (or C++ templates (:-)). > Do I understand this correctly? Is there away around it? Keep in mind > that what I want to express is the notion in a base class that Operation > X needs to be present, but the precise parameters to X will be > determined by the child class. Why this is needed? How this class might be used? Consider the most general case, I have an instance of some class member: procedure Foo (Object : in out T'Class) is begin X (Object, ?); Now I am going to apply X to Object. How do I know the parameter profile? There is no way to determine it. What cannot be used with class is not of the class. So X with varying parameter profile cannot be an operation of the class, unless you bring that variance under one roof. One way to do it is to pack all possible actual profiles into values of one other type P or P'Class: type P is abstract tagged ...; -- All possible parameters type T is abstract tagged ...; -- The type procedure X (Object : in out T; Parameters : P'Class) is abstract; A client have to override X. It can also derive from P its own set of parameters. BTW, this is the typical case where multiple dispatch would be appropriate and more safe: procedure X (Object : in out T; Parameters : P) is abstract; Like in: Print (Output : in out Device; Object : Thing) is abstract; Without multiple dispatch each client should take care of selecting an appropriate way of dealing with all possible parameters that may occur in P'Class. Here we come to another problem. You can say, OK, but each client has its own unique set of parameters. Why should I care about the cross-variants coming from other clients? This is the problem of parallel type hierarchies. When you derive from T you want either to automatically derive or to force a deriving from P. These pairs of derived types have to be bound. In terms of multiple dispatch it is the case when the joint dispatch table has to be diagonal. The most famous examples are Object-Container_Of, Object-Handle types pairs. When you derive a new object, you also want to have a specialized handle type (or access type) to it. AFAIK, this problem is not solved in either Ada or any other OO language I know. > I'm interested *specifically* in how this > works with an abstract tagged record and the inheritance heierarchy that > goes with it, so please don't suggest that I'm going about it all wrong > and I really need to do something with a generic, etc... If my > understanding is correct and it can't be done then we just trash that > notion and go to other techniques, but I want to know how this > capability works. Thanks. And so we return to the very first question, why there should be a class? -- Regards, Dmitry A. Kazakov http://www.dmitry-kazakov.de ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: Abstract Operations On A Tagged Record 2004-10-30 13:04 Abstract Operations On A Tagged Record Marin David Condic 2004-10-30 14:34 ` Dmitry A. Kazakov @ 2004-10-30 16:07 ` Nick Roberts 2004-11-01 12:58 ` Marin David Condic 1 sibling, 1 reply; 7+ messages in thread From: Nick Roberts @ 2004-10-30 16:07 UTC (permalink / raw) Marin David Condic wrote: > Can someone clarify my understanding (perhaps again) on abstract > operations and the implications as a class heierarchy is being > constructed? Specifically, what I want is something like this: > > A base class has an abstract operation, but I may not yet know the data > types of the parameters - or possibly even the number of parameters. > ... > The base class wants to express the basic necessity of "Some_Op" but it > really doesn't know if it will have 1, 2 or N parameters or of what > type. My understanding is that if I express that as an abstract > operation, when I get to the child class, I'll have to supply a real > operation of the same name and same parameter profile. Correct. > (Or can I use a > different parameter profile & satisfy the need for a concrete > implementation of the abstract op? No. > My recollection is the compiler > whines about it or creates an overloading. Is this correct? Correct. (The compiler whines ;-) > I realize I can make another procedure "Some_Op" with a different > parameter list and that overloading will take care of me, but then I > didn't really need the abstract procedure in the first place, did I? It > becomes unnecessary baggage that you have to fill in just because its > there, but you have no intention of using it. Worse than that, you have no dynamic dispatching, which is the whole point of using a tagged type in the first place. Disaster. > Do I understand this correctly? Is there away around it? The solution is simple, Marin. You use /two/ type hierarchies, one to represent the primary objects, and a secondary one to represent the detailed parameters of the primitive operation in question. For example, suppose we have a primary hierarchy of shapes, and we want a primitive operation transforming a shape according to a set of parameters that depends on the shape (and the kind of transformation): type Root_Shape_Type is abstract tagged private; type Transformation_Spec is abstract tagged private; function Transform (Shape: in Root_Shape_Type; Spec: in Transformation_Spec'Class) return Root_Shape_Type'Class is abstract; Transformation_Error: exception; Note carefully that Transform is a primitive operation of the primary type Root_Shape_Type. The Spec parameter is of a class wide type, and so is the result (since a transformation might produce a different kind of shape). We could then declare some transformations, such as: type Rotation is new Transformation_Spec with record Angle: Radians; end record; type Translation is new Transformation_Spec with record Offset: Surface_Offset; end record; and we might declare a polygon, say, as follows: type Polygonal_Shape is new Root_Shape_Type with private; function Points (Shape: in Polygonal_Shape) return Point_Vector; ... -- other polygon operations function Transform (Shape: in Polygonal_Shape; Spec: in Transformation_Spec'Class) return Root_Shape_Type'Class; The body of the Transform function for polygons would have to test the actual type of the Spec parameter to determine which tranformation to perform: function Transform (Shape: in Polygonal_Shape; Spec: in Transformation_Spec'Class) return Root_Shape_Type'Class is begin if Spec in Rotation then declare Rot_Spec: constant Rotation := Rotation(Spec); Center: constant Point := Centerpoint(Shape); Old_Points: constant Point_Vector := Points(Shape); New_Points: Point_Triple(Old_Points'Range); begin for P in Old_Points'Range loop New_Points(P) := Rotate( Old_Points(P), Center, Rot_Spec.Angle ); end loop; return To_Polygon(New_Points); end; elsif Spec in Translation then ... else raise Transformation_Error; -- unrecognized transformation end if; end Transform; I think it is interesting to note that some languages, such as Cecil, provide a way to select an operation's implementation (body) on two or more parameters, rather than just one. Cecil is a very interesting language (but not yet mature enough for real use, I think). Nevertheless, Ada's lowly single selection still permits new shapes to be added to a program without having to worry about how this affects other parts of the program (in general), and this could be very useful. Note also that you might possibly wish to swap the roles of shape and transformation, so that Transform becomes a primitive operation of Transformation_Spec (instead of Root_Shape_Type), and bodies of the function have to test for different shapes. This would allow you to add new transformations in a very independent manner. HTH -- Nick Roberts ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: Abstract Operations On A Tagged Record 2004-10-30 16:07 ` Nick Roberts @ 2004-11-01 12:58 ` Marin David Condic 2004-11-01 18:09 ` Nick Roberts 0 siblings, 1 reply; 7+ messages in thread From: Marin David Condic @ 2004-11-01 12:58 UTC (permalink / raw) Thanks for the response. As both you and Dimitry observed, I rather neglected to toss in the class parameter. I should have put in an example with a base class operation that looked like: procedure Some_Op (The_Thing : in out Some_Tagged_Type'Class) is abstract ; Then the intent was to discuss the need to have child operations with different parameter profiles: procedure Some_Op (The_Thing : in out Some_Tagged_Type_Child1 ; Parm1 : in Integer) is abstract ; procedure Some_Op (The_Thing : in out Some_Tagged_Type_Child2 ; Parm1 : in Boolean; Parm_2 : in Float) is abstract ; My understanding from your explanation is that I can't really get around having to fill in *something* for: procedure Some_Op (The_Thing : in out Some_Tagged_Type'Class) is abstract ; but that overloading takes care of the other cases. I was hoping to avoid having a useless operation dangling around like that. However, I had thought of the case you mention wherein I could provide another tagged type for the parameter list and do something like this in the base class: procedure Some_Op (The_Thing : in out Some_Tagged_Type'Class; Its_Parms : in Another_Tagged_Type'Class) is abstract ; That sounds like a reasonable answer to the abstraction problem I had. I can identify the need for some parameters down the line without having to specify what they are in the base class. It keeps me from proliferating operations all over the place while allowing me to insist that a certain operation must be provided from anything inherited from the base class. Thanks for the help. MDC Nick Roberts wrote: > Marin David Condic wrote: > >> Can someone clarify my understanding (perhaps again) on abstract >> operations and the implications as a class heierarchy is being >> constructed? Specifically, what I want is something like this: >> >> A base class has an abstract operation, but I may not yet know the >> data types of the parameters - or possibly even the number of >> parameters. ... >> The base class wants to express the basic necessity of "Some_Op" but >> it really doesn't know if it will have 1, 2 or N parameters or of what >> type. My understanding is that if I express that as an abstract >> operation, when I get to the child class, I'll have to supply a real >> operation of the same name and same parameter profile. > > > Correct. > >> (Or can I use a different parameter profile & satisfy the need for a >> concrete implementation of the abstract op? > > > No. > >> My recollection is the compiler whines about it or creates an >> overloading. Is this correct? > > > Correct. (The compiler whines ;-) > >> I realize I can make another procedure "Some_Op" with a different >> parameter list and that overloading will take care of me, but then I >> didn't really need the abstract procedure in the first place, did I? >> It becomes unnecessary baggage that you have to fill in just because >> its there, but you have no intention of using it. > > > Worse than that, you have no dynamic dispatching, which is the whole > point of using a tagged type in the first place. Disaster. > >> Do I understand this correctly? Is there away around it? > > > The solution is simple, Marin. You use /two/ type hierarchies, one to > represent the primary objects, and a secondary one to represent the > detailed parameters of the primitive operation in question. > > For example, suppose we have a primary hierarchy of shapes, and we > want a primitive operation transforming a shape according to a set > of parameters that depends on the shape (and the kind of > transformation): > > type Root_Shape_Type is abstract tagged private; > > type Transformation_Spec is abstract tagged private; > > function Transform (Shape: in Root_Shape_Type; > Spec: in Transformation_Spec'Class) > return Root_Shape_Type'Class is abstract; > > Transformation_Error: exception; > > Note carefully that Transform is a primitive operation of the primary > type Root_Shape_Type. The Spec parameter is of a class wide type, and so > is the result (since a transformation might produce a different kind of > shape). > > We could then declare some transformations, such as: > > type Rotation is new Transformation_Spec with > record > Angle: Radians; > end record; > > type Translation is new Transformation_Spec with > record > Offset: Surface_Offset; > end record; > > and we might declare a polygon, say, as follows: > > type Polygonal_Shape is new Root_Shape_Type with private; > > function Points (Shape: in Polygonal_Shape) return Point_Vector; > > ... -- other polygon operations > > function Transform (Shape: in Polygonal_Shape; > Spec: in Transformation_Spec'Class) > return Root_Shape_Type'Class; > > The body of the Transform function for polygons would have to test the > actual type of the Spec parameter to determine which tranformation to > perform: > > function Transform (Shape: in Polygonal_Shape; > Spec: in Transformation_Spec'Class) > return Root_Shape_Type'Class is > begin > if Spec in Rotation then > declare > Rot_Spec: constant Rotation := Rotation(Spec); > Center: constant Point := Centerpoint(Shape); > Old_Points: constant Point_Vector := Points(Shape); > New_Points: Point_Triple(Old_Points'Range); > begin > for P in Old_Points'Range loop > New_Points(P) := > Rotate( Old_Points(P), Center, Rot_Spec.Angle ); > end loop; > return To_Polygon(New_Points); > end; > elsif Spec in Translation then > ... > else > raise Transformation_Error; -- unrecognized transformation > end if; > end Transform; > > I think it is interesting to note that some languages, such as Cecil, > provide a way to select an operation's implementation (body) on two or > more parameters, rather than just one. Cecil is a very interesting > language (but not yet mature enough for real use, I think). > > Nevertheless, Ada's lowly single selection still permits new shapes to > be added to a program without having to worry about how this affects > other parts of the program (in general), and this could be very useful. > > Note also that you might possibly wish to swap the roles of shape and > transformation, so that Transform becomes a primitive operation of > Transformation_Spec (instead of Root_Shape_Type), and bodies of the > function have to test for different shapes. This would allow you to > add new transformations in a very independent manner. > > HTH > -- ====================================================================== Marin David Condic I work for: http://www.belcan.com/ My project is: http://www.jsf.mil/NSFrames.htm Send Replies To: m o d c @ a m o g c n i c . r "Power corrupts. Absolute power is kind of neat" -- John Lehman, Secretary of the Navy 1981-1987 ====================================================================== ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: Abstract Operations On A Tagged Record 2004-11-01 12:58 ` Marin David Condic @ 2004-11-01 18:09 ` Nick Roberts 2004-11-01 22:27 ` Marin David Condic 2004-11-06 13:17 ` Marin David Condic 0 siblings, 2 replies; 7+ messages in thread From: Nick Roberts @ 2004-11-01 18:09 UTC (permalink / raw) Marin David Condic wrote: > Thanks for the response. As both you and Dimitry observed, I rather > neglected to toss in the class parameter. I should have put in an > example with a base class operation that looked like: > > procedure Some_Op (The_Thing : in out Some_Tagged_Type'Class) > is abstract ; Although this declaration isn't actually illegal, it probably isn't what you intended, Marin. Since Some_Op is not a primitive operation of any named type, it cannot be inherited, and so cannot be overridden. It therefore does not ensure that an operation is implemented by any derived type. I'm fairly certain that you require something like: procedure Some_Op (The_Thing : in out Some_Tagged_Type; The_Params: in Some_Parameter_Type'Class) is abstract ; where Some_Parameter_Type is a tagged type (it could also be abstract) from which you derive types to specify particular variations and their specific parameter values, just as I (and Dmitry) illustrated. At the point where you /call/ Some_Op, you may well provide a class wide actual parameter, for The_Thing, for The_Params, or both, e.g.: declare This_Thing: constant Some_Tagged_Type'Class := From_Outer_Space; These_Params: constant Some_Parameter_Type'Class := What_To_Do; begin Some_Op( This_Thing, These_Params ); The call of Some_Op, in this example, will dispatch on the actual type of the class wide object This_Thing. Because it dispatches, it will call an concrete (non-abstract) overriding of Some_Op, for a concrete actual type (derived from Some_Tagged_Type). Does this make sense? -- Nick Roberts ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: Abstract Operations On A Tagged Record 2004-11-01 18:09 ` Nick Roberts @ 2004-11-01 22:27 ` Marin David Condic 2004-11-06 13:17 ` Marin David Condic 1 sibling, 0 replies; 7+ messages in thread From: Marin David Condic @ 2004-11-01 22:27 UTC (permalink / raw) Yes. I was originally tossing something out as "This is what I desire even if the language doesn't accommodate me..." I understand that without the parameters tacked on, the original base class operation isn't going to do anything useful for me. (Other than remind me I need to make something that *does* do the job later...) I think the best answer is to include another tagged type for the variable parameter content that is to come in the child classes. Thanks. MDC Nick Roberts wrote: > > Does this make sense? > -- ====================================================================== Marin David Condic I work for: http://www.belcan.com/ My project is: http://www.jsf.mil/NSFrames.htm Send Replies To: m o d c @ a m o g c n i c . r "Power corrupts. Absolute power is kind of neat" -- John Lehman, Secretary of the Navy 1981-1987 ====================================================================== ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: Abstract Operations On A Tagged Record 2004-11-01 18:09 ` Nick Roberts 2004-11-01 22:27 ` Marin David Condic @ 2004-11-06 13:17 ` Marin David Condic 1 sibling, 0 replies; 7+ messages in thread From: Marin David Condic @ 2004-11-06 13:17 UTC (permalink / raw) BTW: I've been mucking around with this for the last few days and, after much struggling, have concluded that attempting to do this (while it may indeed work) started becoming entirely too unwieldy. Trying to keep stalling off the problems with more and more layers of indirection started becoming painful. What I'm doing is basically leaving those operations out of the base class and (where it makes sense) I'm creating layer(s) of intermediate classes that get more and more specific about what the required parameter types are. Its still rather "heavy" (you have to keep isolating things in more packages and creating more branches in the class tree) but it seemed less painful than trying to find all the necessary abstractions & provide all the needed operations on the parameter class. Sometimes you just have to bite the bullet and say "here's a version that deals with a float and here's a version that deals with a boolean and its just not worth the pain of trying to make them both be the same thing..." But at least I got a better understanding of what I'm dealing with, eh? MDC Nick Roberts wrote: > > I'm fairly certain that you require something like: > > procedure Some_Op (The_Thing : in out Some_Tagged_Type; > The_Params: in Some_Parameter_Type'Class) > is abstract ; > -- ====================================================================== Marin David Condic I work for: http://www.belcan.com/ My project is: http://www.jsf.mil/NSFrames.htm Send Replies To: m o d c @ a m o g c n i c . r "Power corrupts. Absolute power is kind of neat" -- John Lehman, Secretary of the Navy 1981-1987 ====================================================================== ^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2004-11-06 13:17 UTC | newest] Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2004-10-30 13:04 Abstract Operations On A Tagged Record Marin David Condic 2004-10-30 14:34 ` Dmitry A. Kazakov 2004-10-30 16:07 ` Nick Roberts 2004-11-01 12:58 ` Marin David Condic 2004-11-01 18:09 ` Nick Roberts 2004-11-01 22:27 ` Marin David Condic 2004-11-06 13:17 ` Marin David Condic
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox