* Access types as parameters @ 2009-07-17 8:39 Rick 2009-07-17 15:03 ` Adam Beneschan 0 siblings, 1 reply; 31+ messages in thread From: Rick @ 2009-07-17 8:39 UTC (permalink / raw) Given: type My_Type is ...; and type My_Access_Type is access all My_Type'Class; what is the practical difference between: function My_Function (Thing : access My_Type'Class) return Positive; and function My_Function (Thing : My Access_Type return Positive; ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Access types as parameters 2009-07-17 8:39 Access types as parameters Rick @ 2009-07-17 15:03 ` Adam Beneschan 2009-07-17 16:28 ` Hibou57 (Yannick Duchêne) 0 siblings, 1 reply; 31+ messages in thread From: Adam Beneschan @ 2009-07-17 15:03 UTC (permalink / raw) On Jul 17, 1:39 am, Rick <rickdu...@gmail.com> wrote: > Given: > > type My_Type is ...; > and > type My_Access_Type is access all My_Type'Class; > > what is the practical difference between: > > function My_Function (Thing : access My_Type'Class) return > Positive; > and > function My_Function (Thing : My Access_Type return Positive; The accessibility level rules say that a value of type My_Access_Type cannot point to an object that might go away before My_Access_Type does. (Unless you use 'Unchecked_Access to create the access value.) This is to prevent dangling references. The rules apply to parameters also, so if you call the second My_Function, you can't give it the 'Access of an object that is deeper than My_Access_Type. If My_Access_Type is declared at library level (i.e. in a library package, not inside a procedure or function), then you can't say procedure Some_Procedure is X : aliased My_Type; --or some type derived from My_Type begin My_Function (X'access); -- i.e. the second My_Function But this restriction doesn't apply to the first My_Function. Since that My_Function has an access parameter, you can pass the 'Access of any object of the right type, no matter how deeply nested inside procedures the object is. -- Adam ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Access types as parameters 2009-07-17 15:03 ` Adam Beneschan @ 2009-07-17 16:28 ` Hibou57 (Yannick Duchêne) 2009-07-17 23:25 ` rickduley 0 siblings, 1 reply; 31+ messages in thread From: Hibou57 (Yannick Duchêne) @ 2009-07-17 16:28 UTC (permalink / raw) Adam has already given a good answer, but I may say a bit the same in shorter words : access My_Type'Class in a function declaration, is a so called “ anonymous type ”, and as this type is only declared locally, it cannot match any other types defined at wider level (beceause this would simply not be type conformant). access My_Type'Class does not have a so wide validity as My_Access_Type may have. This is conveniant and safe More words.... As an hint : you may use My_Access_Type if the reference is to be stored into a record (as an exemple) and may use access My_Type'Class if you only need it locally. Looking at the function signature, you may then quicly see what kind of things the function may allowed it- self to do with the reference you gave it. If you see the function wants an “ access My_Type'Class ”, you nearly do not have to bother about anything, but if you see it wants a “ My_Access_Type ” you may need to be sure there is a good collaboration between the caller and the callee. This is anyway, a good invitation to read the function documentation (if comments are provided about its usage). But this does not means “ access My_Type'Class ” is better beceause it is “ safer ” (quotes, beceause the other way is not always not safe), as sometime, My_Access_Type is mandatory, depending on what the function have to do with the reference. ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Access types as parameters 2009-07-17 16:28 ` Hibou57 (Yannick Duchêne) @ 2009-07-17 23:25 ` rickduley 2009-07-18 1:03 ` Randy Brukardt 0 siblings, 1 reply; 31+ messages in thread From: rickduley @ 2009-07-17 23:25 UTC (permalink / raw) Cc: rickduley On Jul 18, 12:28 am, Hibou57 (Yannick Duchêne) <yannick_duch...@yahoo.fr> wrote: > Adam has already given a good answer, but I may say a bit the same in > shorter words : > > access My_Type'Class in a function declaration, is a so called “ > anonymous type ”, and as this type is only declared locally, it cannot > match any other types defined at wider level (beceause this would > simply not be type conformant). > > access My_Type'Class does not have a so wide validity as > My_Access_Type may have. > > This is conveniant and safe > > More words.... > > As an hint : you may use My_Access_Type if the reference is to be > stored into a record (as an exemple) and may use access My_Type'Class > if you only need it locally. Looking at the function signature, you > may then quicly see what kind of things the function may allowed it- > self to do with the reference you gave it. > > If you see the function wants an “ access My_Type'Class ”, you nearly > do not have to bother about anything, but if you see it wants a “ > My_Access_Type ” you may need to be sure there is a good collaboration > between the caller and the callee. This is anyway, a good invitation > to read the function documentation (if comments are provided about its > usage). > > But this does not means “ access My_Type'Class ” is better beceause it > is “ safer ” (quotes, beceause the other way is not always not safe), > as sometime, My_Access_Type is mandatory, depending on what the > function have to do with the reference. Hi Yannick Thanks for that. I don't quite see what you mean by: > But this does not means “ access My_Type'Class ” is better beceause it > is “ safer ” (quotes, beceause the other way is not always not safe), > as sometime, My_Access_Type is mandatory, depending on what the > function have to do with the reference. If the function is called, and the actual parameter is valid, what difference can it make what the function does with the data? ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Access types as parameters 2009-07-17 23:25 ` rickduley @ 2009-07-18 1:03 ` Randy Brukardt 2009-07-19 22:57 ` rickduley 0 siblings, 1 reply; 31+ messages in thread From: Randy Brukardt @ 2009-07-18 1:03 UTC (permalink / raw) [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #1: Type: text/plain, Size: 1836 bytes --] "rickduley" <rickduley@gmail.com> wrote in message news:8410fc60-9b8a-4f82-92fc-622a6bbe5931@i18g2000pro.googlegroups.com... > I don't quite see what you mean by: > >> But this does not means � access My_Type'Class � is better beceause it >> is � safer � (quotes, beceause the other way is not always not safe), >> as sometime, My_Access_Type is mandatory, depending on what the >> function have to do with the reference. > >If the function is called, and the actual parameter is valid, what >difference can it make what the function does with the data? He might be referring to the fact that the uses of the anonymous access parameter might raise Program_Error (as there is a dynamic accessibility check) while the named example is either going to be legal (and work properly) or illegal. My answer to the original question would have been: >Given: > > type My_Type is ...; >and > type My_Access_Type is access all My_Type'Class; > >what is the practical difference between: > > function My_Function (Thing : access My_Type'Class) return Positive; >and > function My_Function (Thing : My Access_Type) return Positive; The first has a runtime parameter passing overhead that the latter does not, and depending on how the parameter is used inside of the function, a significant possibility of raising Program_Error later. (Something Bob Duff calls a "tripping hazard"). This latter problem is especially bad as it is likely to be missed in unit testing (in that sense, it is similar to assuming the lower bound of a string is 1 - tests often make the same assumption). So it is best to avoid the first form unless you have a particular need for dispatching on an access value (which won't happen here, because the designated type is class-wide). Randy. ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Access types as parameters 2009-07-18 1:03 ` Randy Brukardt @ 2009-07-19 22:57 ` rickduley 2009-07-20 0:10 ` John B. Matthews ` (3 more replies) 0 siblings, 4 replies; 31+ messages in thread From: rickduley @ 2009-07-19 22:57 UTC (permalink / raw) Hi Randy You wrote: > So it is best to avoid the first form unless you have a particular need for > dispatching on an access value (which won't happen here, because the > designated type is class-wide). Why then does GtkAda consistently use the first form, i.e.: function My_Function (Thing : access My_Type'Class) return Positive; for an 'Initialize' function? It actually uses the form (this for Gtk.Button.Gtk_Button): procedure Initialize (Button : access Gtk_Button_Record'Class; Label : UTF8_String); ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Access types as parameters 2009-07-19 22:57 ` rickduley @ 2009-07-20 0:10 ` John B. Matthews 2009-07-20 8:13 ` Dmitry A. Kazakov ` (2 subsequent siblings) 3 siblings, 0 replies; 31+ messages in thread From: John B. Matthews @ 2009-07-20 0:10 UTC (permalink / raw) In article <e325296c-7114-4540-9406-b7426da408dd@f18g2000prf.googlegroups.com>, rickduley <rickduley@gmail.com> wrote: > Hi Randy > > You wrote: > > So it is best to avoid the first form unless you have a particular need for > > dispatching on an access value (which won't happen here, because the > > designated type is class-wide). > > Why then does GtkAda consistently use the first form, i.e.: > function My_Function (Thing : access My_Type'Class) return Positive; > for an 'Initialize' function? IIUC, "Thing" typically points to an object managed by the Gtk library. It's outside the scope of Ada's accessibility rules. > It actually uses the form (this for Gtk.Button.Gtk_Button): > procedure Initialize > (Button : access Gtk_Button_Record'Class; > Label : UTF8_String); The source mentions the section "Creating your own widgets" in the user's guide. The section "Creating new widgets in Ada" addresses the Initialize procedure explicitly: <http://libre.adacore.com/wp-content/files/auto_update/gtkada-docs/ gtkada_ug/gtkada_ug.html> -- John B. Matthews trashgod at gmail dot com <http://sites.google.com/site/drjohnbmatthews> ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Access types as parameters 2009-07-19 22:57 ` rickduley 2009-07-20 0:10 ` John B. Matthews @ 2009-07-20 8:13 ` Dmitry A. Kazakov 2009-07-21 0:34 ` Randy Brukardt 2009-07-21 14:34 ` Adam Beneschan 3 siblings, 0 replies; 31+ messages in thread From: Dmitry A. Kazakov @ 2009-07-20 8:13 UTC (permalink / raw) On Sun, 19 Jul 2009 15:57:12 -0700 (PDT), rickduley wrote: >> So it is best to avoid the first form unless you have a particular need for >> dispatching on an access value (which won't happen here, because the >> designated type is class-wide). > > Why then does GtkAda consistently use the first form, alas! > i.e.: > function My_Function (Thing : access My_Type'Class) return Positive; > for an 'Initialize' function? You mean function My_Function (Thing : access Gtk_XXX_Record'Class) return Positive; I think it should better be function My_Function (Thing : Gtk_XXX) return Positive; since GtkAda has Gtk_XXX declared as access to every Gtk_XXX_Record'Class. > It actually uses the form (this for Gtk.Button.Gtk_Button): > procedure Initialize > (Button : access Gtk_Button_Record'Class; > Label : UTF8_String); Well, this is likely a design bug. Gtk_Object_Record should have been limited controlled with Initialize inherited from Ada.Finalization.Limited_Controlled. -- Regards, Dmitry A. Kazakov http://www.dmitry-kazakov.de ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Access types as parameters 2009-07-19 22:57 ` rickduley 2009-07-20 0:10 ` John B. Matthews 2009-07-20 8:13 ` Dmitry A. Kazakov @ 2009-07-21 0:34 ` Randy Brukardt 2009-07-21 14:34 ` Adam Beneschan 3 siblings, 0 replies; 31+ messages in thread From: Randy Brukardt @ 2009-07-21 0:34 UTC (permalink / raw) "rickduley" <rickduley@gmail.com> wrote in message news:e325296c-7114-4540-9406-b7426da408dd@f18g2000prf.googlegroups.com... > Hi Randy > > You wrote: >> So it is best to avoid the first form unless you have a particular need >> for >> dispatching on an access value (which won't happen here, because the >> designated type is class-wide). > > Why then does GtkAda consistently use the first form, i.e.: > function My_Function (Thing : access My_Type'Class) return Positive; > for an 'Initialize' function? > > It actually uses the form (this for Gtk.Button.Gtk_Button): > procedure Initialize > (Button : access Gtk_Button_Record'Class; > Label : UTF8_String); You'd have to ask the designers of GTKAda that question. I think it's terrible to define Ada-level interfaces that way, but they may have had good reasons. See Claw (http://www.rrsoftware.com/html/prodinf/claw/clawintro.html for the free version) to see how I think a real-world Ada GUI library ought to be designed. (Well, more accurately, how I thought 10 years ago, I'd make some minor changes today, mainly making most of the types limited, as that works much better in Ada nowadays). Randy. Randy. ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Access types as parameters 2009-07-19 22:57 ` rickduley ` (2 preceding siblings ...) 2009-07-21 0:34 ` Randy Brukardt @ 2009-07-21 14:34 ` Adam Beneschan 2009-07-23 2:11 ` Stephen Leake 3 siblings, 1 reply; 31+ messages in thread From: Adam Beneschan @ 2009-07-21 14:34 UTC (permalink / raw) On Jul 19, 3:57 pm, rickduley <rickdu...@gmail.com> wrote: > Hi Randy > > You wrote: > > So it is best to avoid the first form unless you have a particular need for > > dispatching on an access value (which won't happen here, because the > > designated type is class-wide). > > Why then does GtkAda consistently use the first form, i.e.: > function My_Function (Thing : access My_Type'Class) return Positive; > for an 'Initialize' function? > > It actually uses the form (this for Gtk.Button.Gtk_Button): > procedure Initialize > (Button : access Gtk_Button_Record'Class; > Label : UTF8_String); Well, for a *function*, using an access parameter can help get around the rule that you can't have an IN OUT parameter to a function (a rule that I think is going away in the next revision of the language). By making it an anonymous access type, rather than a named access type, that lets you pass local variables that the function can modify. For a procedure, there's less reason to do so. I don't know anything about GTK, so I don't know why this wouldn't have worked, if all Initialize is doing is to set up some fields in Button: procedure Initialize (Button : in out Gtk_Button_Record'Class; Label : UTF8_String); If, on the other hand, Initialize needs Button as an access so that it can store it in a data structure, then it probably would have been best to make it a named access type, to ensure that dangling references aren't stored in that data structure. If Initialize is an imported routine from a C library---well, in that case, I guess you do whatever works. But I'd still think that using "in out" would have the same effect---the address of the record is passed. (In fact, for an anonymous access parameter, the code normally CANNOT pass *just* an address; there has to be additional information in case the procedure needs to do an accessibility level check. But for a procedure imported from C, the parameter passing mechanisms may be different.) Randy is right, and I thought about saying something along those lines in my first response but decided (maybe unwisely) not to complicate things. You don't need to pass an anonymous access parameter if all you're going to do is modify the accessed object---IN OUT will do. And if you're going to store the access parameter in a global structure, you'll get a runtime error if you try to store a dangling reference, so it's best to use a global access type so that accessibility level errors are caught at compile time. There may be some esoteric situations, other than dispatching, where passing an anonymous access parameter may be useful: perhaps the procedure can determine at runtime whether to store an access value or make a copy of the object depending on the accessibility level, or perhaps the procedure wants the ability to accept NULL to indicate that there is no object to modify, or perhaps a procedure takes two access parameters and sets up the two accessed objects to point to each other. But those all seem very unusual, and may be due to an inferior design anyway. -- Adam ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Access types as parameters 2009-07-21 14:34 ` Adam Beneschan @ 2009-07-23 2:11 ` Stephen Leake 2009-08-11 23:41 ` Randy Brukardt 0 siblings, 1 reply; 31+ messages in thread From: Stephen Leake @ 2009-07-23 2:11 UTC (permalink / raw) Adam Beneschan <adam@irvine.com> writes: > For a procedure, there's less reason to do so. I don't know anything > about GTK, so I don't know why this wouldn't have worked, if all > Initialize is doing is to set up some fields in Button: > > procedure Initialize > (Button : in out Gtk_Button_Record'Class; > Label : UTF8_String); > > If, on the other hand, Initialize needs Button as an access so that it > can store it in a data structure, then it probably would have been > best to make it a named access type, to ensure that dangling > references aren't stored in that data structure. Most GTK subprograms do need access values; the same is true of any system that deals with derived types at multiple levels. The only library-level object that can hold any type in the hierarchy is a class-wide pointer. Lists of objects must hold pointers, etc. So one reason to use 'access' instead of 'in out' is simply to avoid the user having to type '.all' everywhere. A reason to use 'access Record_Type' instead of 'in Access_Type' is to avoid explicit type conversions. Given: package Widget is type Gtk_Widget_Record is ...; type Gtk_Widget is access all Gtk_Widget_Record'class; procedure Show (Widget : in Gtk_Widget); end Widget; package Window is type Gtk_Window_Record is new Gtk_Widget_Record with ...; type Gtk_Window is access all Gtk_Window_Record'class; end Window; Window : Gtk_Window := ...; then this does not work: Widget.Show (Window); but this does: Widget.Show (Gtk_Widget (Window)); This is just annoying! However, if Show is declared: procedure Show (Widget : access constant Gtk_Widget_Record'class); Then Show matches any access type in the class hierarchy; Widget.Show (Window); works. In addition, leaving out the 'class makes Show a primitive operation, which has many advantages. I've been struggling with this issue in OpenToken, and settled on using 'access Record_Type' as the best compromise. Now I just need to add 'constant' in all the right places; that wasn't allowed in Ada 95, when OpenToken was first written. In some cases, I have both class-wide and primitive operations: type Instance is abstract tagged private; subtype Class is Instance'Class; type Handle is access all Class; function Name (Token : in Instance) return String is abstract; -- Dispatching calls to Name function Name_Dispatch (Token : in Class) return String; function Name_Dispatch (Token : access constant Instance'Class) return String; That gives the best of all cases, at the expense of writting more code. > And if you're going to store the access parameter in a global > structure, you'll get a runtime error if you try to store a dangling > reference, so it's best to use a global access type so that > accessibility level errors are caught at compile time. This is a problem; people keep stumbling across it. Still, you get used to it after a while, and stop trying to pass 'access of a local variable. -- -- Stephe ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Access types as parameters 2009-07-23 2:11 ` Stephen Leake @ 2009-08-11 23:41 ` Randy Brukardt 2009-08-12 2:22 ` Stephen Leake 0 siblings, 1 reply; 31+ messages in thread From: Randy Brukardt @ 2009-08-11 23:41 UTC (permalink / raw) I know this is old, but it is so misguided that it needs a reply...especially as it is from someone who usually knowledgable on this forum. - RLB "Stephen Leake" <stephen_leake@stephe-leake.org> wrote in message news:u63dkb12m.fsf@stephe-leake.org... ... > Most GTK subprograms do need access values; the same is true of any > system that deals with derived types at multiple levels. The only > library-level object that can hold any type in the hierarchy is a > class-wide pointer. Lists of objects must hold pointers, etc. All of the indefinite containers can hold class-wide objects. No (explicit) pointers are needed. One of the goals for the next revision of Ada is that the vast majority of reasonable data structures can be reasonably written with the Ada.Containers. In particular, the multiway tree container can be used to model almost all forms for tree structure (and many graphs as well). That should mean that the need for explicit access types will be greatly reduced when class-wide programming. Now, of course, GTK was designed for Ada 95, and those options weren't available then, so misguided choices were more justifiable. ... > A reason to use 'access Record_Type' instead of 'in Access_Type' is to > avoid explicit type conversions. This is a terrible reason: explicit conversions show when you are converting accesses from one "domain of use" to another. (I'm thinking of each named access type as a "domain-of-use". It only makes sense to have multiple named access types for a particular designated type if there are different "domains-of-use" [such as different storage pools or disjoint data structures].) The type conversions make it clear when you are converting from one "domain-of-use" to another, something that should be rare. Anonymous access types hide that, and make it essentially impossible to determine what the storage pool of an access is -- meaning that most values that pass through anonymous access cannot be deallocated even if converted to a named type (because there is no way to know if it is the *right* named type). [Technically, they can be deallocated, but the program is erroneous and may do anything.] Finally, the implicit conversions are only allow to the anonymous type. To get back to a named type, you have to go back to explicit conversions, so you don't really save anything. In your example: > Given: > > package Widget is > type Gtk_Widget_Record is ...; > type Gtk_Widget is access all Gtk_Widget_Record'class; > > procedure Show (Widget : in Gtk_Widget); > end Widget; > > package Window is > type Gtk_Window_Record is new Gtk_Widget_Record with ...; > type Gtk_Window is access all Gtk_Window_Record'class; > end Window; > > Window : Gtk_Window := ...; > > then this does not work: > > Widget.Show (Window); > > but this does: > > Widget.Show (Gtk_Widget (Window)); > > This is just annoying! > > However, if Show is declared: > > procedure Show (Widget : access constant Gtk_Widget_Record'class); > > Then Show matches any access type in the class hierarchy; > > Widget.Show (Window); > > works. Of course, Show in this case surely should be dispatching (even if the interface itself doesn't use that property) so that extensions can extend the operation, so the declaration should be: procedure Show (Widget : in Gtk_Widget); and the call should be Show (Window.all); Probably the real reason the designers of GTK made this an anonymous access parameter is to get that dispatching property. It surely has little to do with implicit conversions. [The fact that you can dispatch on access types in Ada, but only with this bizarre syntax is a highly misguided feature of the language IMHO. It was added only because of the IN OUT parameter restrictions and the insistence of some that that not being able to copy lousy C++ and Java designs directly (rather than better Ada designs not using explicit access types) would harm the language's use in some way.] Anyway, the real issue that these implicit conversions don't work in many cases that you would expect them to. For instance, inside of Show, if you need to treat the parameter as a value of GTK_Widget: Widget_List : GTK_Widget; Widget_List := Widget; -- Illegal. Anon to Named conversions not implicit! Widget_List := GTK_Widget(Widget); Indeed, the only reason (in Ada 95) why the anonymous access conversions are implicit is the obvious problem that they don't have a name! Else I think they would have in fact required a conversion. It's possible that we'd going to fix this conversion problem in the next version of Ada, but we will have to do more study to ensure that we don't introduce a show-stopping incompatibility. ... > I've been struggling with this issue in OpenToken, and settled on > using 'access Record_Type' as the best compromise. Now I just need to add > 'constant' in all the right places; that wasn't allowed in Ada 95, > when OpenToken was first written. That may have made sense in Ada 95, but now it is a terrible choice, as it forces the user to do all memory management on the objects. If you force passing access-to-objects, you cannot store the objects in a container and allow the container to do the storage management. (It also requires adding a huge amount of noise to stack-allocated objects -- the need to declare everything "aliased" and 'access on every call -- and the use of stack-allocated objects should always be a primary goal. Why OpenToken cannot be designed to usually use stack-allocated objects escapes me...) > In some cases, I have both class-wide and primitive operations: > > type Instance is abstract tagged private; > subtype Class is Instance'Class; > type Handle is access all Class; > > function Name (Token : in Instance) return String is abstract; > > -- Dispatching calls to Name > function Name_Dispatch (Token : in Class) return String; > function Name_Dispatch (Token : access constant Instance'Class) return > String; > > That gives the best of all cases, at the expense of writting more > code. Let me get this straight. You declare an extra routine with a much longer name and a complex (and expensive at runtime call-sequence) in order that you can avoid writing ".all" periodically?? That is, if you have type Any_Instance_Class is access all Instance'Class; An_Instance : Any_Instance_Class; (and appropriate visibility), you would rather write: Name_Dispatch (An_Instance); rather than Name (An_Instance.all); ??? Moreover, if you are using Ada 2005, you probably should write: An_Instance.Name And you don't even need the .all anymore! (Nor do you need "appropriate visibility"!) Talk about a lot of work for nothing. ;-) >> And if you're going to store the access parameter in a global >> structure, you'll get a runtime error if you try to store a dangling >> reference, so it's best to use a global access type so that >> accessibility level errors are caught at compile time. > > This is a problem; people keep stumbling across it. Still, you get > used to it after a while, and stop trying to pass 'access of a local > variable. One would hope that designers of Ada interfaces would talk more care for the users than to say "they get used to it after a while". That sort of ticking time bomb (that easily can be missed in unit testing!) is very adverse to the goals of safety espoused by many in this group (including me!). It has the potential of making Ada into just another programming language. Randy. ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Access types as parameters 2009-08-11 23:41 ` Randy Brukardt @ 2009-08-12 2:22 ` Stephen Leake 2009-08-13 1:06 ` Randy Brukardt 0 siblings, 1 reply; 31+ messages in thread From: Stephen Leake @ 2009-08-12 2:22 UTC (permalink / raw) "Randy Brukardt" <randy@rrsoftware.com> writes: > I know this is old, but it is so misguided that it needs a > reply...especially as it is from someone who usually knowledgable on this > forum. - RLB Thanks :). > "Stephen Leake" <stephen_leake@stephe-leake.org> wrote in message > news:u63dkb12m.fsf@stephe-leake.org... > ... >> Most GTK subprograms do need access values; the same is true of any >> system that deals with derived types at multiple levels. The only >> library-level object that can hold any type in the hierarchy is a >> class-wide pointer. Lists of objects must hold pointers, etc. > > All of the indefinite containers can hold class-wide objects. No (explicit) > pointers are needed. You mean the language defined containers in Ada.Containers.*. Yes, the pointers are not explicit, but they are needed in the bodies. That's how the language works. Gtk has its own container hierarchy. So it needs pointers. > One of the goals for the next revision of Ada is that the vast majority of > reasonable data structures can be reasonably written with the > Ada.Containers. I agree this is a reasonable approach for many applications, but trying to use Ada.Containers to implement the Gtk container hierarchy would be a nightmare. > In particular, the multiway tree container can be used to model > almost all forms for tree structure (and many graphs as well). That > should mean that the need for explicit access types will be greatly > reduced when class-wide programming. This is true, but only if starting from scratch in a new system. And it's not always the best solution anyway; each application has particular needs. >> A reason to use 'access Record_Type' instead of 'in Access_Type' is to >> avoid explicit type conversions. > > This is a terrible reason: explicit conversions show when you are converting > accesses from one "domain of use" to another. Not in this case. The domain is always "some Gtk object". Or "some OpenToken object". > (I'm thinking of each named access type as a "domain-of-use". It > only makes sense to have multiple named access types for a > particular designated type if there are different "domains-of-use" > [such as different storage pools or disjoint data structures].) GtkAda and OpenToken don't have different named access types for one type; they have different named access types for types in a hierarchy. That allows the user to have collections of "any Gtk object", and collections of "Gtk windows" or "my dialog objects". All of these occur in a real Gtk application, and all need their own access type. > The type conversions make it clear when you are converting from one > "domain-of-use" to another, something that should be rare. "converting" from one level in the type hierarchy to another is not rare. That's part of the point of a type hierarchy. Most of the time, it's just a view conversion, not a "real" conversion. To be convincing, I'd have to post an example from Gtk or OpenToken written both ways; maybe later I'll find time for that. > Anonymous access types hide that, and make it essentially impossible to > determine what the storage pool of an access is -- meaning that most values > that pass through anonymous access cannot be deallocated even if converted > to a named type (because there is no way to know if it is the *right* named > type). [Technically, they can be deallocated, but the program is erroneous > and may do anything.] That's true. But it just forces better application design. Deallocating should be done by the same chunk of code that allocates, so this shouldn't be an issue. That is how OpenToken works internally. > Finally, the implicit conversions are only allow to the anonymous type. To > get back to a named type, you have to go back to explicit conversions, so > you don't really save anything. In your example: > >> Given: >> >> package Widget is >> type Gtk_Widget_Record is ...; >> type Gtk_Widget is access all Gtk_Widget_Record'class; >> >> procedure Show (Widget : in Gtk_Widget); >> end Widget; >> >> package Window is >> type Gtk_Window_Record is new Gtk_Widget_Record with ...; >> type Gtk_Window is access all Gtk_Window_Record'class; >> end Window; >> >> Window : Gtk_Window := ...; >> >> then this does not work: >> >> Widget.Show (Window); >> >> but this does: >> >> Widget.Show (Gtk_Widget (Window)); >> >> This is just annoying! >> >> However, if Show is declared: >> >> procedure Show (Widget : access constant Gtk_Widget_Record'class); >> >> Then Show matches any access type in the class hierarchy; >> >> Widget.Show (Window); >> >> works. > > Of course, Show in this case surely should be dispatching (even if the > interface itself doesn't use that property) so that extensions can extend > the operation, so the declaration should be: > procedure Show (Widget : in Gtk_Widget); You meant Gtk_Widget_Record here. > and the call should be > Show (Window.all); You can redesign the requirements (decide that show "should be dispatching"), but that's just avoiding the issue. Surely you can accept that some subprograms should not be dispatching! > Probably the real reason the designers of GTK made this an anonymous access > parameter is to get that dispatching property. no, because the type is 'class. Some other subprograms are dispatching; that's not the critical issue here. > It surely has little to do with implicit conversions. This requires mindreading. In _my_ mind, implicit conversions are a major reason for this design. It would be interesting to hear from the GtkAda designers. I suspect they would say "that was the only way we could get it to work in Ada 95" or something similar. > [The fact that you can dispatch on access types in Ada, but only with this > bizarre syntax is a highly misguided feature of the language IMHO. It was > added only because of the IN OUT parameter restrictions and the insistence > of some that that not being able to copy lousy C++ and Java designs directly > (rather than better Ada designs not using explicit access types) would harm > the language's use in some way.] It would be interesting to go back and design Ada to accomodate these issues in a different way. But I'm trying to work with the language we have. > Anyway, the real issue that these implicit conversions don't work in many > cases that you would expect them to. For instance, inside of Show, if you > need to treat the parameter as a value of GTK_Widget: > > Widget_List : GTK_Widget; > > Widget_List := Widget; -- Illegal. Anon to Named conversions not > implicit! > Widget_List := GTK_Widget(Widget); Which is as it should be, for accessiblity reasons. If you do this, you have a potential accessibility error, which is bad. So it certainly should not be implicit. > Indeed, the only reason (in Ada 95) why the anonymous access conversions are > implicit is the obvious problem that they don't have a name! Else I think > they would have in fact required a conversion. > > It's possible that we'd going to fix this conversion problem in the next > version of Ada, but we will have to do more study to ensure that we don't > introduce a show-stopping incompatibility. Or introduce lots of "unnecessary" explicit conversions. I hope that discussion will be on the ada-comment list, where I can monitor and/or participate. >> I've been struggling with this issue in OpenToken, and settled on >> using 'access Record_Type' as the best compromise. Now I just need to add >> 'constant' in all the right places; that wasn't allowed in Ada 95, >> when OpenToken was first written. > > That may have made sense in Ada 95, but now it is a terrible choice, as it > forces the user to do all memory management on the objects. Yes. But in this case, the intent is to declare a few objects on the heap, and leave them there forever. Some of them can be on the stack in some simple situations. > If you force passing access-to-objects, you cannot store the objects > in a container and allow the container to do the storage management. yes, but it's not a problem in practice. For other applications, it might be more of a problem. > (It also requires adding a huge amount of noise to stack-allocated > objects -- the need to declare everything "aliased" and 'access on > every call -- and the use of stack-allocated objects should always > be a primary goal. Why OpenToken cannot be designed to usually use > stack-allocated objects escapes me...) Here's one example. We have a grammar: L -> E EOF E -> T {+ T} T -> F {* F} F -> ( E ) F -> integer This has recursion in one place; E refers to itself via T and F. Recursion in a data structure requires pointers. More complex grammars have more recursion, and require more pointers. OpenToken allows for the most complex grammars, so it assumes pointers for all grammars. If you use Ada.Containers, you can replace the pointers with Cursor values, but that just complicates things; at this level, Cursor values are just pointers with a different (and more cumbersome) syntax. You will probably say "Cursor values are _safe_, pointers are not". That's true. I just don't find the extra safety to be worth the hassle. But to be fair, I have not tried to implement something as complex as OpenToken using only Ada.Containers. So maybe the hassle isn't as bad as I think it will be; what I know how to do is _always_ easier than something I haven't tried yet :). I have used Ada.Containers in a small project. It was a mind-bending experience, but the result is elegant. >> In some cases, I have both class-wide and primitive operations: >> >> type Instance is abstract tagged private; >> subtype Class is Instance'Class; >> type Handle is access all Class; >> >> function Name (Token : in Instance) return String is abstract; >> >> -- Dispatching calls to Name >> function Name_Dispatch (Token : in Class) return String; >> function Name_Dispatch (Token : access constant Instance'Class) return >> String; >> >> That gives the best of all cases, at the expense of writting more >> code. > > Let me get this straight. You declare an extra routine with a much longer > name and a complex (and expensive at runtime call-sequence) in order that > you can avoid writing ".all" periodically?? That's not the only, or even the main, reason. There were many places where I wrote "Name (foo)" that turned out to be not dispatching, when I wanted it to be dispatching. That was confusing, and difficult to track down. Hence the longer name. I have Emacs programmed to put in (or take out) the .all where necessary (in response to compiler errors), so that's not a big deal for me. > That is, if you have > type Any_Instance_Class is access all Instance'Class; > An_Instance : Any_Instance_Class; > > (and appropriate visibility), > > you would rather write: > Name_Dispatch (An_Instance); > > rather than > Name (An_Instance.all); Yes, because then I can be _sure_ it is dispatching. In most cases, the immediate argument is _not_ classwide, but is in fact a parent type view of a descendant type object, so I want the call to dispatch, to get the full descendant type object name. Saving the .all is gravy. > Moreover, if you are using Ada 2005, you probably should write: > An_Instance.Name > > And you don't even need the .all anymore! (Nor do you need "appropriate > visibility"!) That feels like going over to the dark side of C++, so I've been avoiding it. But I admit it is attractive sometimes. But it doesn't address the issue of forcing dispatching. >>> And if you're going to store the access parameter in a global >>> structure, you'll get a runtime error if you try to store a dangling >>> reference, so it's best to use a global access type so that >>> accessibility level errors are caught at compile time. >> >> This is a problem; people keep stumbling across it. Still, you get >> used to it after a while, and stop trying to pass 'access of a local >> variable. > > One would hope that designers of Ada interfaces would talk more care for the > users than to say "they get used to it after a while". That sort of ticking > time bomb (that easily can be missed in unit testing!) is very adverse to > the goals of safety espoused by many in this group (including me!). It has > the potential of making Ada into just another programming language. Yes. I did say using "access" was a compromise. I can't go back and redesign Ada. Nor do I want to reimplement OpenToken to take full advantage of Ada 2005 features. I agree that new projects, that are not bound by Ada 95 heritage, should strive harder to use Ada.Containers and avoid access parameters. -- -- Stephe ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Access types as parameters 2009-08-12 2:22 ` Stephen Leake @ 2009-08-13 1:06 ` Randy Brukardt 2009-08-13 8:34 ` Niklas Holsti 2009-08-18 12:22 ` Stephen Leake 0 siblings, 2 replies; 31+ messages in thread From: Randy Brukardt @ 2009-08-13 1:06 UTC (permalink / raw) "Stephen Leake" <stephen_leake@stephe-leake.org> wrote in message news:uprb1epp8.fsf@stephe-leake.org... > "Randy Brukardt" <randy@rrsoftware.com> writes: >> "Stephen Leake" <stephen_leake@stephe-leake.org> wrote in message >> news:u63dkb12m.fsf@stephe-leake.org... >> ... >>> Most GTK subprograms do need access values; the same is true of any >>> system that deals with derived types at multiple levels. The only >>> library-level object that can hold any type in the hierarchy is a >>> class-wide pointer. Lists of objects must hold pointers, etc. >> >> All of the indefinite containers can hold class-wide objects. No >> (explicit) >> pointers are needed. > > You mean the language defined containers in Ada.Containers.*. Yes, the > pointers are not explicit, but they are needed in the bodies. That's > how the language works. Not necessarily, in that Ada.Containers does not need to be implemented in Ada. Some other magic could have been used. But in any case, any implementation pointers are hidden, not exposed in the interface. The fact that they might exist ought to be irrelevant. > Gtk has its own container hierarchy. So it needs pointers. Which would be misguided in a new design, but obviously it wouldn't make sense to try to replace an old working design just for this reason. >> One of the goals for the next revision of Ada is that the vast majority >> of >> reasonable data structures can be reasonably written with the >> Ada.Containers. > > I agree this is a reasonable approach for many applications, but > trying to use Ada.Containers to implement the Gtk container hierarchy > would be a nightmare. You surely don't *implement* some old containers with Ada.Containers, you replace them. And surely you have to get rid of all access types from the interfaces if you want to do so. This is not the sort of project that anyone is likely to do on existing code (for good reasons). It's solely intended for new code. >> In particular, the multiway tree container can be used to model >> almost all forms for tree structure (and many graphs as well). That >> should mean that the need for explicit access types will be greatly >> reduced when class-wide programming. > > This is true, but only if starting from scratch in a new system. And > it's not always the best solution anyway; each application has > particular needs. There is no solution that will handle all needs, but Ada.Containers (or Ada.Bounded_Containers) should handle at least 95% of the possible needs. It should only be necessary to "roll your own" when at least two of extreme performance, extreme control over resource use, and unusual data structures are needed. Keep in mind that the Ada.Containers gives you memory management for free (much harder to make mistakes) and hopefully will be nearly as easy to use in Ada 201Z as writing straight code (given iterator and accessor syntax improvements). At least that's the intent. A certain amount of FUD and unfamilarity will probably prevent people from using the containers as much as they ought to be. >>> A reason to use 'access Record_Type' instead of 'in Access_Type' is to >>> avoid explicit type conversions. >> >> This is a terrible reason: explicit conversions show when you are >> converting >> accesses from one "domain of use" to another. > > Not in this case. The domain is always "some Gtk object". Or "some > OpenToken object". That's broken. If OpenToken is parsing more than one (unrelated) stream, it makes perfect sense to keep the objects related to those parsings in different domains. If you are not allowing this, you are restricting the applicability of the library unnecessarily. (It's not as obvious that this matters to GTK; I think it does -- we certainly allow this sort of thing in Claw -- but a case can be made that there is only one GUI to write too.) >> (I'm thinking of each named access type as a "domain-of-use". It >> only makes sense to have multiple named access types for a >> particular designated type if there are different "domains-of-use" >> [such as different storage pools or disjoint data structures].) > > GtkAda and OpenToken don't have different named access types for one > type; they have different named access types for types in a hierarchy. IMHO, the library (specification) shouldn't have *any* access types, named or otherwise. [I realize this is an ideal; occassionally, you need reference semantics for a function result and thus have to use an access type. But you never need to do that for a parameter.] In any case, I'm talking about the client's access types, generally one per domain-of-use. > That allows the user to have collections of "any Gtk object", and > collections of "Gtk windows" or "my dialog objects". All of these > occur in a real Gtk application, and all need their own access type. If the user needs a collection of "any GTK object", they can create one with their own access type, or with an appropriate Ada.Containers instance. You don't need to put that into the interface of the library to get that sort of functionality. When you do, you just greatly limit the user's memory management options. (And I realize that no one is going to change GTK anytime soon; I'm talking about how libraries ought to be when built from scratch. But no one should be under any illusions that GTK is a good design, especially for current versions of Ada.) >> The type conversions make it clear when you are converting from one >> "domain-of-use" to another, something that should be rare. > > "converting" from one level in the type hierarchy to another is not > rare. That's part of the point of a type hierarchy. Most of the time, > it's just a view conversion, not a "real" conversion. If you're changing levels, you are not "converting" at all, because all of these use the same named access type. (Such named access types will almost always be access-to-class-wide.) So there is no conversion at all going on. Indeed, if you have a type hierarchy, you have OOP, and the way you go between levels is with dispatching. Again, there is no conversions. I really don't understand why some designers want to declare access types all over the place. There should be exactly one for each "domain-of-use" (such as "any GTK window for the local terminal"). But "domain-of-use" is a client decision, not something the library should be deciding. > To be convincing, I'd have to post an example from Gtk or OpenToken > written both ways; maybe later I'll find time for that. Yeah, I know that we are both talking rather hypothetically, and we may have rather different examples in mind. >> Anonymous access types hide that, and make it essentially impossible to >> determine what the storage pool of an access is -- meaning that most >> values >> that pass through anonymous access cannot be deallocated even if >> converted >> to a named type (because there is no way to know if it is the *right* >> named >> type). [Technically, they can be deallocated, but the program is >> erroneous >> and may do anything.] > > That's true. But it just forces better application design. > > Deallocating should be done by the same chunk of code that allocates, > so this shouldn't be an issue. Given that it is the client that determines have memory management is done for a particular use, you are right to an extent. But only if you never try to store the anonymous access in any way, which in my experience with Claw is an impossible restriction. > That is how OpenToken works internally. The client should be determining the memory management of the visible objects (only it can know if stack allocation, container allocator, or full dynamic allocation is needed). Hidden stuff, of course, had better be managed by the library. ... > You can redesign the requirements (decide that show "should be > dispatching"), but that's just avoiding the issue. Surely you can > accept that some subprograms should not be dispatching! It's interesting that later you essentially said that Show should be primitive (which is the same as dispatching in Ada for OO types like these). But be that as it may, my answer is that for OO types (tagged types), no, I think that all routines should either be class-wide or dispatching. A routine which is neither is highly suspicious and indicative of a bug. (I know that there is an issue with constructors, but I actually prefer them to be dispatching so that they are required to be overridden, either with something useful or with "raise Program_Error". I wouldn't insist on this last part in a programming guide, however.) ... >> It surely has little to do with implicit conversions. > > This requires mindreading. In _my_ mind, implicit conversions are a > major reason for this design. Then it is a load of crap. IMHO. (But no sense in us arguing it...) > It would be interesting to hear from the GtkAda designers. I suspect > they would say "that was the only way we could get it to work in Ada > 95" or something similar. Right. ... >>> I've been struggling with this issue in OpenToken, and settled on >>> using 'access Record_Type' as the best compromise. Now I just need to >>> add >>> 'constant' in all the right places; that wasn't allowed in Ada 95, >>> when OpenToken was first written. >> >> That may have made sense in Ada 95, but now it is a terrible choice, as >> it >> forces the user to do all memory management on the objects. > > Yes. But in this case, the intent is to declare a few objects on the > heap, and leave them there forever. Some of them can be on the stack > in some simple situations. Claw had a similar intent, yet we decided to let the client make the memory management decisions. Thus you can now use an Ada.Containers instance to make a list of windows, and we didn't have to make any changes to Claw to accomidate this use. >> If you force passing access-to-objects, you cannot store the objects >> in a container and allow the container to do the storage management. > > yes, but it's not a problem in practice. For other applications, it > might be more of a problem. >> (It also requires adding a huge amount of noise to stack-allocated >> objects -- the need to declare everything "aliased" and 'access on >> every call -- and the use of stack-allocated objects should always >> be a primary goal. Why OpenToken cannot be designed to usually use >> stack-allocated objects escapes me...) > > Here's one example. We have a grammar: > > L -> E EOF > E -> T {+ T} > T -> F {* F} > F -> ( E ) > F -> integer > > This has recursion in one place; E refers to itself via T and F. > > Recursion in a data structure requires pointers. Sure, but you surely don't have to expose any of those pointers in the interface! At worst, you expose "handles" or "cursors" in the manner that the containers do, in order that you don't expose clients to all of the erroneous behavior that comes from the misuse of pointers. > More complex grammars have more recursion, and require more pointers. > > OpenToken allows for the most complex grammars, so it assumes pointers > for all grammars. And I guess that's my objection. > If you use Ada.Containers, you can replace the pointers with > Cursor values, but that just complicates things; at this level, Cursor > values are just pointers with a different (and more cumbersome) syntax. No, Cursors are *abstracted* pointers, with the details hidden. Sure, hiding *always* complicates things, but it also buys you clearer interfaces (no questions about who is supposed to allocate things) and potentially extra checking (dangling cursors can usually be detected cheaply, whereas dangling pointers usually just corrupt memory). In any case, the Ada 201Z syntax can be as simple as one extra name. No big deal. Yes, I know it will be a while before we have that in hand, but the extra syntax now isn't that much for the dangling pointer detection that you get (at least from most containers implementations). > You will probably say "Cursor values are _safe_, pointers are not". > That's true. I just don't find the extra safety to be worth the > hassle. For me, the whole reason for using Ada in the first place is that it finds most of my mistakes for me. The ones that cause the most trouble are usually dangling pointers problems. If I can use a way to detect them, I'll never have to find a bug again. ;-) ;-) That's surely worth a little bit of extra work. > But to be fair, I have not tried to implement something as complex as > OpenToken using only Ada.Containers. So maybe the hassle isn't as bad > as I think it will be; what I know how to do is _always_ easier than > something I haven't tried yet :). The main problem with the Ada 2005 containers is the difficulty of updating an element in place. We think that we have solved that fairly cheaply for Ada 201Z (although the idiom is pretty strange and I worry that users won't be able to figure out how it works). > I have used Ada.Containers in a small project. It was a mind-bending > experience, but the result is elegant. Great. I haven't had the opportunity yet (unfortunately). I've been maintaining existing stuff for the most part. >>> In some cases, I have both class-wide and primitive operations: >>> >>> type Instance is abstract tagged private; >>> subtype Class is Instance'Class; >>> type Handle is access all Class; >>> >>> function Name (Token : in Instance) return String is abstract; >>> >>> -- Dispatching calls to Name >>> function Name_Dispatch (Token : in Class) return String; >>> function Name_Dispatch (Token : access constant Instance'Class) return >>> String; >>> >>> That gives the best of all cases, at the expense of writting more >>> code. >> >> Let me get this straight. You declare an extra routine with a much longer >> name and a complex (and expensive at runtime call-sequence) in order that >> you can avoid writing ".all" periodically?? > > That's not the only, or even the main, reason. There were many places > where I wrote "Name (foo)" that turned out to be not dispatching, when > I wanted it to be dispatching. That was confusing, and difficult to > track down. Hence the longer name. This doesn't make sense to me, although I realize Ada 95 had some really serious problems in this area. A call to Name is *always* a dispatching call. The issue could have been that you are getting static binding, but that is almost always a good thing. You almost never want redispatching, and in the rare cases where you do want it, you need to ask for it explicitly with a type conversion. > I have Emacs programmed to put in (or take out) the .all where > necessary (in response to compiler errors), so that's not a big deal > for me. > >> That is, if you have >> type Any_Instance_Class is access all Instance'Class; >> An_Instance : Any_Instance_Class; >> >> (and appropriate visibility), >> >> you would rather write: >> Name_Dispatch (An_Instance); >> >> rather than >> Name (An_Instance.all); > > Yes, because then I can be _sure_ it is dispatching. In most cases, > the immediate argument is _not_ classwide, but is in fact a parent > type view of a descendant type object, so I want the call to dispatch, > to get the full descendant type object name. You must be doing something seriously wrong, because you always get (dynamic) dispatching when the argument is class-wide, and otherwise you get static binding. I thought you were using a bunch of access-to-class-wide types, so how you could *not* get dispatching is beyond me. Well, unless you are converting to the specific type rather than the class-wide type when toward the leaves of the tree -- Ada is pretty picky about this! It is critical to remember that T and T'Class are different types in Ada with different properties and you can't use the interchangably. > Saving the .all is gravy. > >> Moreover, if you are using Ada 2005, you probably should write: >> An_Instance.Name >> >> And you don't even need the .all anymore! (Nor do you need "appropriate >> visibility"!) > > That feels like going over to the dark side of C++, so I've been > avoiding it. But I admit it is attractive sometimes. But it doesn't > address the issue of forcing dispatching. If you really need to force dispatching, convert to the appropriate class-wide type: Some_Type'Class(An_Instance.all).Name or Name (Some_Type'Class(An_Instance.all)); (we have to stick the .all in, unfortunately). The use of 'Class is what forces dispatching in Ada. But this should be pretty rare. It's certainly not necessary in this example as An_Instance is an access-to-class-wide type and thus the call is definitely dispatching. In any case, it should be pretty rare in OO code where you need to "force" dispatching. Usually, Ada does the right thing automatically. The main problem I had with Claw was using type conversions to force the correct resolution of a call for the visibility I had, and that can screw up the dispatching. The solution (now) is often is the use of the prefix notation, which pretty much ignores visibility issues and thus will resolve in many more cases. I suspect that you ran into the same problem, but fixed it by clogging up the interfaces (which does work, of course, just not something I'd recommend). Randy. ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Access types as parameters 2009-08-13 1:06 ` Randy Brukardt @ 2009-08-13 8:34 ` Niklas Holsti 2009-08-13 9:15 ` Dmitry A. Kazakov 2009-08-14 4:07 ` Randy Brukardt 2009-08-18 12:22 ` Stephen Leake 1 sibling, 2 replies; 31+ messages in thread From: Niklas Holsti @ 2009-08-13 8:34 UTC (permalink / raw) Randy Brukardt wrote: > You almost never want redispatching That's interesting as my experience is the opposite: Most of the calls between primitive methods in my code use redispatching. My tagged types tend to have a hierarchy of primitive operations, for example an Overall_Method that implements some functionality by a sequence of calls to a Detail_Method. Some derived types override Detail_Method, so the call from Overall_Method to Detail_Method must redispatch. Some other (or the same) derived types override Overall_Method, so it cannot be class-wide. Perhaps the frequency of redispatching depends on the application domain, or on personal programming style. The frequency of redispatching might me an interesting OO "metric". -- Niklas Holsti Tidorum Ltd niklas holsti tidorum fi . @ . ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Access types as parameters 2009-08-13 8:34 ` Niklas Holsti @ 2009-08-13 9:15 ` Dmitry A. Kazakov 2009-08-13 20:13 ` Niklas Holsti 2009-08-14 4:07 ` Randy Brukardt 1 sibling, 1 reply; 31+ messages in thread From: Dmitry A. Kazakov @ 2009-08-13 9:15 UTC (permalink / raw) On Thu, 13 Aug 2009 11:34:20 +0300, Niklas Holsti wrote: > Randy Brukardt wrote: > >> You almost never want redispatching > > That's interesting as my experience is the opposite: Most of the calls > between primitive methods in my code use redispatching. Shudder... > My tagged types tend to have a hierarchy of primitive operations, for > example an Overall_Method that implements some functionality by a > sequence of calls to a Detail_Method. Some derived types override > Detail_Method, so the call from Overall_Method to Detail_Method must > redispatch. Some other (or the same) derived types override > Overall_Method, so it cannot be class-wide. When this happens to me I always consider it as a strong indication to re-design. > Perhaps the frequency of redispatching depends on the application > domain, or on personal programming style. The frequency of redispatching > might me an interesting OO "metric". Yes. I think the target shall be strictly 0%. I consider re-dispatch is an indication of a language / design problem, because it is a hidden type error. An argument goes as follows. Once you have dispatched to some type T, the corresponding controlling parameter is of this type. Let we converted it to a class. If now it dispatches again to T, then that was just wasting time, if it dispatches to S /= T then it is a type error. -- Regards, Dmitry A. Kazakov http://www.dmitry-kazakov.de ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Access types as parameters 2009-08-13 9:15 ` Dmitry A. Kazakov @ 2009-08-13 20:13 ` Niklas Holsti 2009-08-13 21:07 ` Dmitry A. Kazakov 0 siblings, 1 reply; 31+ messages in thread From: Niklas Holsti @ 2009-08-13 20:13 UTC (permalink / raw) Dmitry A. Kazakov wrote: > On Thu, 13 Aug 2009 11:34:20 +0300, Niklas Holsti wrote: > >> Randy Brukardt wrote: >> >>> You almost never want redispatching >> That's interesting as my experience is the opposite: Most of the calls >> between primitive methods in my code use redispatching. > > Shudder... Also an interesting reaction. > I consider re-dispatch is an indication of a language / design problem, > because it is a hidden type error. An argument goes as follows. Once you > have dispatched to some type T, the corresponding controlling parameter is > of this type. Let we converted it to a class. If now it dispatches again to > T, then that was just wasting time, if it dispatches to S /= T then it is a > type error. To me that is a very abstract and formalistic argument that is easily weaker than the practical benefits (providing sensible but overridable default behaviour while avoding code duplication) that make me use redispatching. What is more, a central benefit of inheritance is that an object can be viewed as being of parent type T *and also* of derived type S, depending on the context and your needs, so I don't accept that redispatching "to a different type" implies a type error. -- Niklas Holsti Tidorum Ltd niklas holsti tidorum fi . @ . ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Access types as parameters 2009-08-13 20:13 ` Niklas Holsti @ 2009-08-13 21:07 ` Dmitry A. Kazakov 2009-08-14 9:27 ` Niklas Holsti 0 siblings, 1 reply; 31+ messages in thread From: Dmitry A. Kazakov @ 2009-08-13 21:07 UTC (permalink / raw) On Thu, 13 Aug 2009 23:13:39 +0300, Niklas Holsti wrote: > Dmitry A. Kazakov wrote: >> On Thu, 13 Aug 2009 11:34:20 +0300, Niklas Holsti wrote: >> >>> Randy Brukardt wrote: >>> >>>> You almost never want redispatching >>> That's interesting as my experience is the opposite: Most of the calls >>> between primitive methods in my code use redispatching. >> >> Shudder... > > Also an interesting reaction. > >> I consider re-dispatch is an indication of a language / design problem, >> because it is a hidden type error. An argument goes as follows. Once you >> have dispatched to some type T, the corresponding controlling parameter is >> of this type. Let we converted it to a class. If now it dispatches again to >> T, then that was just wasting time, if it dispatches to S /= T then it is a >> type error. > > To me that is a very abstract and formalistic argument that is easily > weaker than the practical benefits (providing sensible but overridable > default behaviour while avoding code duplication) that make me use > redispatching. If some formal premises are not satisfied (a type consistency is one of them) then it is meaningless to talk about benefits. In an extreme case, you might have an erroneous program that occasionally yields a correct result. Would you see a practical benefit here? > What is more, a central benefit of inheritance is that an > object can be viewed as being of parent type T *and also* of derived > type S, depending on the context and your needs, so I don't accept that > redispatching "to a different type" implies a type error. An object of the type S is already substitutable for T in the primitive operations of T and the operations of T'Class. Why do you want to re-dispatch? Re-dispatch means that an object pulls a trail of its history of type conversions (view conversion is a case of) throughout all substitutions of the object. This is a sufficiently more complex programming model, which semantics is doubtful. Instead of plain type T you have "S as T", or "S from R seen as T", and so on. That eliminates the advantage of clean Ada model that distinguishes T and T'Class. Your initial example was about certain decomposition of operations into class-wide and primitive, so that a primitive operation would call to class-wide ones. My point that such cases should be attributed to poor design or maybe to a language problem. One argument I gave was type inconsistency. There are also practical arguments that dispatching calls are slower, that re-dispatch requires referential semantics, that there would be no chance to have classes of by-value types like Integer etc. -- Regards, Dmitry A. Kazakov http://www.dmitry-kazakov.de ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Access types as parameters 2009-08-13 21:07 ` Dmitry A. Kazakov @ 2009-08-14 9:27 ` Niklas Holsti 2009-08-14 10:36 ` Dmitry A. Kazakov 0 siblings, 1 reply; 31+ messages in thread From: Niklas Holsti @ 2009-08-14 9:27 UTC (permalink / raw) Dmitry A. Kazakov wrote: > On Thu, 13 Aug 2009 23:13:39 +0300, Niklas Holsti wrote: > >> Dmitry A. Kazakov wrote: >>> On Thu, 13 Aug 2009 11:34:20 +0300, Niklas Holsti wrote: >>> >>>> Randy Brukardt wrote: >>>> >>>>> You almost never want redispatching >>>> That's interesting as my experience is the opposite: Most of the calls >>>> between primitive methods in my code use redispatching. >>> Shudder... >> Also an interesting reaction. >> >>> I consider re-dispatch is an indication of a language / design problem, >>> because it is a hidden type error. An argument goes as follows. Once you >>> have dispatched to some type T, the corresponding controlling parameter is >>> of this type. Let we converted it to a class. If now it dispatches again to >>> T, then that was just wasting time, if it dispatches to S /= T then it is a >>> type error. >> To me that is a very abstract and formalistic argument that is easily >> weaker than the practical benefits (providing sensible but overridable >> default behaviour while avoding code duplication) that make me use >> redispatching. > > If some formal premises are not satisfied (a type consistency is one of > them) then it is meaningless to talk about benefits. In an extreme case, > you might have an erroneous program that occasionally yields a correct > result. Would you see a practical benefit here? As I said later in the message to which you are replying, I don't agree that redispatching implies a type inconsistency. Do you use the word "erroneous" in the formal Ada sense, "erroneous execution"? Then could you please explain what kind of erroneous execution redispatching can cause, that cannot occur without using redispatching? As far as I understand, redispatching itself is safe; erroneous execution can only come from code in the operations that are invoked. If your argument is just that a program with redispatching is more complex and thus more likely to contain programming errors, I partly agree; redispatching makes the sequence of invoked operations more dynamic and thus possibly harder to grasp and make correct. But this argument applies as well to any kind of run-time dispatching, not just to *re*-dispatching. > Your initial example was about certain decomposition of operations into > class-wide and primitive, so that a primitive operation would call to > class-wide ones. No, I think you mistunderstood my example, perhaps because I did not explain it clearly enough. The two operations in my example are both primitive, and can be independently overridden with behaviour suitable to derived types. The operations call each other with redispatching to activate the most suitable behaviour of each operation for the actual type of object. > My point that such cases should be attributed to poor > design In the mind of the beholder, I think. You haven't shown *why* this design is "poor", except by your own design rules. > There are also practical arguments that dispatching calls > are slower, I don't expect to get something for free. Dispatching calls are slower than statically bound calls, right? But you still use dispatching calls when needed, right? > that re-dispatch requires referential semantics, I don't see why, but I don't really care, as I have accepted (albeit reluctantly) that I need referential semantics anyway. -- Niklas Holsti Tidorum Ltd niklas holsti tidorum fi . @ . ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Access types as parameters 2009-08-14 9:27 ` Niklas Holsti @ 2009-08-14 10:36 ` Dmitry A. Kazakov 2009-08-14 16:03 ` Niklas Holsti 0 siblings, 1 reply; 31+ messages in thread From: Dmitry A. Kazakov @ 2009-08-14 10:36 UTC (permalink / raw) On Fri, 14 Aug 2009 12:27:02 +0300, Niklas Holsti wrote: > Dmitry A. Kazakov wrote: >> On Thu, 13 Aug 2009 23:13:39 +0300, Niklas Holsti wrote: >> >>> Dmitry A. Kazakov wrote: >>>> On Thu, 13 Aug 2009 11:34:20 +0300, Niklas Holsti wrote: >>>> >>>>> Randy Brukardt wrote: >>>>> >>>>>> You almost never want redispatching >>>>> That's interesting as my experience is the opposite: Most of the calls >>>>> between primitive methods in my code use redispatching. >>>> Shudder... >>> Also an interesting reaction. >>> >>>> I consider re-dispatch is an indication of a language / design problem, >>>> because it is a hidden type error. An argument goes as follows. Once you >>>> have dispatched to some type T, the corresponding controlling parameter is >>>> of this type. Let we converted it to a class. If now it dispatches again to >>>> T, then that was just wasting time, if it dispatches to S /= T then it is a >>>> type error. >>> To me that is a very abstract and formalistic argument that is easily >>> weaker than the practical benefits (providing sensible but overridable >>> default behaviour while avoding code duplication) that make me use >>> redispatching. >> >> If some formal premises are not satisfied (a type consistency is one of >> them) then it is meaningless to talk about benefits. In an extreme case, >> you might have an erroneous program that occasionally yields a correct >> result. Would you see a practical benefit here? > > As I said later in the message to which you are replying, I don't agree > that redispatching implies a type inconsistency. > > Do you use the word "erroneous" in the formal Ada sense, "erroneous > execution"? Then could you please explain what kind of erroneous > execution redispatching can cause, that cannot occur without using > redispatching? As far as I understand, redispatching itself is safe; > erroneous execution can only come from code in the operations that are > invoked. It was merely a counterexample to "benefit" over "formalism". "Benefit" is bounded by "formalism", not otherwise. If we consider re-dispatch doubtful for formal reasons, that overrides any benefits (or we change the formalism). > If your argument is just that a program with redispatching is more > complex and thus more likely to contain programming errors, I partly > agree; redispatching makes the sequence of invoked operations more > dynamic and thus possibly harder to grasp and make correct. But this > argument applies as well to any kind of run-time dispatching, not just > to *re*-dispatching. I disagree. Dispatching does not make it more complex assuming that all implementations of the primitive operation are conform the interface. You write the client program in terms of another type, of the type T'Class. There is nothing automatically more complex in T'Class than in T. >> Your initial example was about certain decomposition of operations into >> class-wide and primitive, so that a primitive operation would call to >> class-wide ones. > > No, I think you mistunderstood my example, perhaps because I did not > explain it clearly enough. The two operations in my example are both > primitive, and can be independently overridden with behaviour suitable > to derived types. The operations call each other with redispatching to > activate the most suitable behaviour of each operation for the actual > type of object. Yes, I understand this. The question is why it was decomposed in this way. If one operation of the type T calls another operation of the same type. Why should it dispatch. When you call an operation of the type S on T, that is a type error to me. You have circumvented the type system, by an explicit type conversion of T to T'Class. Do not we agree that type conversions should be avoided? >> My point that such cases should be attributed to poor >> design > > In the mind of the beholder, I think. You haven't shown *why* this > design is "poor", except by your own design rules. I think I did. Why do you dispatch again, you already checked the tag. The type is determined, the reader expects the code to correspond that type, you break this convention. My concern is the semantics of the code. Semantically a primitive operation has separate bodies for each type in the class. This gets broken in your design. I.e. it is a third way of how bodies are decomposed. I may agree that the language could offer more than one body per class (class-wide) vs. one body per type (primitive). But so far, a design should conform to the existing model. >> There are also practical arguments that dispatching calls >> are slower, > > I don't expect to get something for free. Dispatching calls are slower > than statically bound calls, right? But you still use dispatching calls > when needed, right? Yes, but only if I cannot statically specify the type. Re-dispatch happens at the places where I already determined the type, but for some reasons am not satisfied with the outcome. Why? Strange... >> that re-dispatch requires referential semantics, > > I don't see why, Because of the type tag. You need same representation for T and T'Class to have T -> T'Class as a view conversion. > but I don't really care, as I have accepted (albeit > reluctantly) that I need referential semantics anyway. But it means that the design relies on referential semantics and cannot be rewritten in terms of value semantics. Formally there should be something fundamentally wrong with that, because there is no reason beyond 1. optimization issues, 2. outer world communication, why referential semantics is chosen. Yet we rely on it. Why? -- Regards, Dmitry A. Kazakov http://www.dmitry-kazakov.de ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Access types as parameters 2009-08-14 10:36 ` Dmitry A. Kazakov @ 2009-08-14 16:03 ` Niklas Holsti 2009-08-15 9:47 ` Dmitry A. Kazakov 0 siblings, 1 reply; 31+ messages in thread From: Niklas Holsti @ 2009-08-14 16:03 UTC (permalink / raw) Dmitry A. Kazakov wrote: > On Fri, 14 Aug 2009 12:27:02 +0300, Niklas Holsti wrote: > >> Dmitry A. Kazakov wrote: >>> On Thu, 13 Aug 2009 23:13:39 +0300, Niklas Holsti wrote: >>> >>>> Dmitry A. Kazakov wrote: >>>>> On Thu, 13 Aug 2009 11:34:20 +0300, Niklas Holsti wrote: >>>>> >>>>>> Randy Brukardt wrote: >>>>>> >>>>>>> You almost never want redispatching >>>>>> That's interesting as my experience is the opposite: Most of the calls >>>>>> between primitive methods in my code use redispatching. >>>>> Shudder... >>>> Also an interesting reaction. >>>> >>>>> I consider re-dispatch is an indication of a language / design problem, >>>>> because it is a hidden type error. An argument goes as follows. Once you >>>>> have dispatched to some type T, the corresponding controlling parameter is >>>>> of this type. Let we converted it to a class. If now it dispatches again to >>>>> T, then that was just wasting time, if it dispatches to S /= T then it is a >>>>> type error. >>>> To me that is a very abstract and formalistic argument that is easily >>>> weaker than the practical benefits (providing sensible but overridable >>>> default behaviour while avoding code duplication) that make me use >>>> redispatching. >>> If some formal premises are not satisfied (a type consistency is one of >>> them) then it is meaningless to talk about benefits. In an extreme case, >>> you might have an erroneous program that occasionally yields a correct >>> result. Would you see a practical benefit here? >> As I said later in the message to which you are replying, I don't agree >> that redispatching implies a type inconsistency. >> >> Do you use the word "erroneous" in the formal Ada sense, "erroneous >> execution"? Then could you please explain what kind of erroneous >> execution redispatching can cause, that cannot occur without using >> redispatching? As far as I understand, redispatching itself is safe; >> erroneous execution can only come from code in the operations that are >> invoked. > > It was merely a counterexample to "benefit" over "formalism". "Benefit" is > bounded by "formalism", not otherwise. If we consider re-dispatch doubtful > for formal reasons, that overrides any benefits (or we change the > formalism). Or we don't agree on the formalism, which seems to be the case for you and me. >> If your argument is just that a program with redispatching is more >> complex and thus more likely to contain programming errors, I partly >> agree; redispatching makes the sequence of invoked operations more >> dynamic and thus possibly harder to grasp and make correct. But this >> argument applies as well to any kind of run-time dispatching, not just >> to *re*-dispatching. > > I disagree. Dispatching does not make it more complex assuming that all > implementations of the primitive operation are conform the interface. You > write the client program in terms of another type, of the type T'Class. > There is nothing automatically more complex in T'Class than in T. If you assume that all operation implementations "conform to the interface" (including semantics) then redispatching is no more complex than dispatching; the effect of the (re)dispatching call is assumed to be the desired one, whatever the actual type of the controlling operand(s). >>> Your initial example was about certain decomposition of operations into >>> class-wide and primitive, so that a primitive operation would call to >>> class-wide ones. >> No, I think you mistunderstood my example, perhaps because I did not >> explain it clearly enough. The two operations in my example are both >> primitive, and can be independently overridden with behaviour suitable >> to derived types. The operations call each other with redispatching to >> activate the most suitable behaviour of each operation for the actual >> type of object. > > Yes, I understand this. The question is why it was decomposed in this way. > If one operation of the type T calls another operation of the same type. > Why should it dispatch. It does *not* call another operation of the same type; it calls another operation of the same *class*. That is what redispatching means, and why the call contains a conversion T->T'Class. > When you call an operation of the type S on T, that > is a type error to me. You have circumvented the type system, by an > explicit type conversion of T to T'Class. Do not we agree that type > conversions should be avoided? I agree that type conversions "should" be avoided, but good grief, sometimes you need them to express something, as in this case, because Ada specifically uses the conversion T->T'Class to invoke dynamic (re)dispatching. And of course this conversion is safe and does not in any way "circumvent" the type system. >>> My point that such cases should be attributed to poor >>> design >> In the mind of the beholder, I think. You haven't shown *why* this >> design is "poor", except by your own design rules. > > I think I did. I disagree (maybe we should have an abbreviation for this, as it seems to occur frequently :-) > Why do you dispatch again, you already checked the tag. Because this gives me the functionality I want, of course. > The type is determined, Of course not, only the point (within the class) of the implementation of the currently executing, (possibly) inherited operation (the caller) is determined. The actual type, as you well know, is any type in T'Class, although it is written "T" in the operation profile. > the reader expects the code to correspond that type, > you break this convention. That depends on the "reader". My convention is different from yours, and I think my convention corresponds more closely to the (full) Ada language, while yours assumes a subset of Ada that follows your design rules. > My concern is the semantics of the code. > Semantically a primitive operation has separate bodies for each type in the > class. Yes, but they are represented by a smaller (sparse) number of bodies in the program text, with the "missing" bodies supplied by inheritance. And that is why the actual type of the actual parameter object is some type in T'Class, not always T (as you well know...) > This gets broken in your design. I.e. it is a third way of how > bodies are decomposed. I may agree that the language could offer more than > one body per class (class-wide) vs. one body per type (primitive). But so > far, a design should conform to the existing model. I fail to understand that. You seem to be claiming that the Ada "model" does not really include redispatching, which is nonsense to me. > Yes, but only if I cannot statically specify the type. Re-dispatch happens > at the places where I already determined the type, The type is *not* already determined; see above. > but for some reasons am > not satisfied with the outcome. Why? Strange... Come on, you know very well that the outcome using redispatching is quite different from the outcome without redispatching. So of course my reason is that I want the former outcome :-) The same outcome could, of course, be gained in another design that does not use redispatching, but at the cost of a different architecture, which (in my view) had significant disadvantages such as code duplication. I suggest that you show how you would design the Divide-and-Conquer example that I described in my reply to Randy earlier today, and then we can compare the two designs. >>> that re-dispatch requires referential semantics, >> I don't see why, > > Because of the type tag. You need same representation for T and T'Class to > have T -> T'Class as a view conversion. That does not imply reference *semantics*, only pass by reference. Not the same thing, as demonstrated by pure functional languages. >> but I don't really care, as I have accepted (albeit >> reluctantly) that I need referential semantics anyway. > > But it means that the design relies on referential semantics and cannot be > rewritten in terms of value semantics. Redispatch does *not* make the design rely on referential semantics. I admit that my design(s) do rely on referential semantics in places, but this is not because of redispatch. > Formally there should be something > fundamentally wrong with that, because there is no reason beyond 1. > optimization issues, 2. outer world communication, why referential > semantics is chosen. Yet we rely on it. Why? In my case, mainly for reason 1. optimization issues. Also because it can sometimes reduce architectural coupling between operations and packages. -- Niklas Holsti Tidorum Ltd niklas holsti tidorum fi . @ . ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Access types as parameters 2009-08-14 16:03 ` Niklas Holsti @ 2009-08-15 9:47 ` Dmitry A. Kazakov 2009-08-15 19:19 ` Niklas Holsti 0 siblings, 1 reply; 31+ messages in thread From: Dmitry A. Kazakov @ 2009-08-15 9:47 UTC (permalink / raw) On Fri, 14 Aug 2009 19:03:52 +0300, Niklas Holsti wrote: > Or we don't agree on the formalism, which seems to be the case for you > and me. Once people agreed on a formalism there would be no place for discussions... (;-)) >>> If your argument is just that a program with redispatching is more >>> complex and thus more likely to contain programming errors, I partly >>> agree; redispatching makes the sequence of invoked operations more >>> dynamic and thus possibly harder to grasp and make correct. But this >>> argument applies as well to any kind of run-time dispatching, not just >>> to *re*-dispatching. >> >> I disagree. Dispatching does not make it more complex assuming that all >> implementations of the primitive operation are conform the interface. You >> write the client program in terms of another type, of the type T'Class. >> There is nothing automatically more complex in T'Class than in T. > > If you assume that all operation implementations "conform to the > interface" (including semantics) then redispatching is no more complex > than dispatching; the effect of the (re)dispatching call is assumed to > be the desired one, whatever the actual type of the controlling operand(s). LSP stuff, huh. No. T already conforms to T'Class, so why do you re-dispatch? The answer is that semantically it does not conform, but the thing you are dispatching to does. That is the complexity. You rely on different behavior of T'Class and T. But according to the interface, T'Class and T are interchangeable. So have broken that convention. >> When you call an operation of the type S on T, that >> is a type error to me. You have circumvented the type system, by an >> explicit type conversion of T to T'Class. Do not we agree that type >> conversions should be avoided? > > I agree that type conversions "should" be avoided, but good grief, > sometimes you need them to express something, as in this case, That is the point. What does it express at the semantic level? Why should it be expressed in this form? > because > Ada specifically uses the conversion T->T'Class to invoke dynamic > (re)dispatching. And of course this conversion is safe and does not in > any way "circumvent" the type system. That depends on definitions. Address to access conversion is also "safe" in some sense. The types involved in a conversion are formally different, which implies different semantics. Once you change the semantics, it is automatically unsafe. >> Why do you dispatch again, you already checked the tag. > > Because this gives me the functionality I want, of course. That is clear, but if you say that the functionality you wanted is re-dispatch itself, that could not justify it. There must be something else in it, which raises the question of design. You have implemented something using re-dispatch. My position is that it is a bad way to implement anything. You disagree... (:-)) >> The type is determined, > > Of course not, only the point (within the class) of the implementation > of the currently executing, (possibly) inherited operation (the caller) > is determined. The actual type, as you well know, is any type in > T'Class, although it is written "T" in the operation profile. No, the actual type is T, just because the operation declaration tells so. If you are in the operation inherited by S from T and you don't want it to be T there, then why do have you inherited and not overridden it in S? Something must be wrong in it. >> the reader expects the code to correspond that type, >> you break this convention. > > That depends on the "reader". My convention is different from yours, and > I think my convention corresponds more closely to the (full) Ada > language, while yours assumes a subset of Ada that follows your design > rules. Are you saying that the reader must be constantly aware that the type of the actual parameter could be not the type the procedure deals with? >> My concern is the semantics of the code. >> Semantically a primitive operation has separate bodies for each type in the >> class. > > Yes, but they are represented by a smaller (sparse) number of bodies in > the program text, with the "missing" bodies supplied by inheritance. And > that is why the actual type of the actual parameter object is some type > in T'Class, not always T (as you well know...) False inheritance cannot serve an argument, see above. You broke it when inherited where you should have overridden. Then are trying to mend things in some third place... >> This gets broken in your design. I.e. it is a third way of how >> bodies are decomposed. I may agree that the language could offer more than >> one body per class (class-wide) vs. one body per type (primitive). But so >> far, a design should conform to the existing model. > > I fail to understand that. You seem to be claiming that the Ada "model" > does not really include redispatching, which is nonsense to me. No it refers to other models of polymorphic operations, like extensible bodies or the thing I described below. >> but for some reasons am >> not satisfied with the outcome. Why? Strange... > > Come on, you know very well that the outcome using redispatching is > quite different from the outcome without redispatching. So of course my > reason is that I want the former outcome :-) Yes, this is the core question. You don't want the semantics of the type T being in an operation of. > The same outcome could, of course, be gained in another design that does > not use redispatching, but at the cost of a different architecture, > which (in my view) had significant disadvantages such as code duplication. > > I suggest that you show how you would design the Divide-and-Conquer > example that I described in my reply to Randy earlier today, and then we > can compare the two designs. type Subproblem is abstract ...; procedure Conquer (X : in out Subproblem) is abstract; type List_Of_Subproblems is ...; -- Container type of Subproblem'Class type Problem is abstract ...; function Divide (X : Problem) return List_Of_Subproblems is abstract; A better case for re-dispatch is an operation defined in terms of other operations. For example x*n defined as x+...+x, n times. you want to have a standard version of *, but in some cases to have an ability to implement it specifically. I am using a sort of this: procedure Add (X : in out Item; Y : Item); procedure Mul (X : in out Item'Class; N : Positive); private function Has_Accelerated_Mul (X : Item) return Boolean; procedure Accelerated_Mul (X : in out Item; N : Positive); procedure Mul (X : in out Item'Class; N : Positive) is begin if Has_Accelerated_Mul (X) then Accelerated_Mult (X, N); else ... -- Do it slowly end if; end Mul; I don't like it, because it exposes Mul as class-wide, while Add is primitive. It would be nice if Ada has supported a kind of decomposition, with a fall back to a class-wide operation. My empirical condition for all dispatching operations is that type tag resolution should move strictly towards the root, and never backwards. I.e. if you have a tag for T'Class,, you can narrow the set of candidates ultimately to the single type, but you never widen the set. >>>> that re-dispatch requires referential semantics, >>> I don't see why, >> >> Because of the type tag. You need same representation for T and T'Class to >> have T -> T'Class as a view conversion. > > That does not imply reference *semantics*, only pass by reference. Formal argument is a view of the actual one. No difference. If Ada had classes of by-value types, then Integer'Class would have a different representation, and Integer -> Integer'Class would be a full scale conversion producing a new object. Consequently there would no way to re-dispatch from Float converted to Integer (in case they were in the same hierarchy of types). -- Regards, Dmitry A. Kazakov http://www.dmitry-kazakov.de ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Access types as parameters 2009-08-15 9:47 ` Dmitry A. Kazakov @ 2009-08-15 19:19 ` Niklas Holsti 2009-08-16 8:32 ` Dmitry A. Kazakov 0 siblings, 1 reply; 31+ messages in thread From: Niklas Holsti @ 2009-08-15 19:19 UTC (permalink / raw) Dmitry A. Kazakov wrote: > On Fri, 14 Aug 2009 19:03:52 +0300, Niklas Holsti wrote: > >> Or we don't agree on the formalism, which seems to be the case for you >> and me. > > Once people agreed on a formalism there would be no place for > discussions... (;-)) Perhaps in the ideal, yes. But formalisms in "design" are not yet that powerful or unambiguous :-) >>> Why do you dispatch again, you already checked the tag. >> Because this gives me the functionality I want, of course. > > That is clear, but if you say that the functionality you wanted is > re-dispatch itself, that could not justify it. There must be something else > in it, which raises the question of design. You have implemented something > using re-dispatch. My position is that it is a bad way to implement > anything. You disagree... (:-)) Well, you still haven't shown me any convincing reason for calling it a bad design, because I don't accept your "type error" argument. >>> The type is determined, >> Of course not, only the point (within the class) of the implementation >> of the currently executing, (possibly) inherited operation (the caller) >> is determined. The actual type, as you well know, is any type in >> T'Class, although it is written "T" in the operation profile. > > No, the actual type is T, just because the operation declaration tells so. This seems to be the origin of our disagreement. You want to view the object as of type T, although at run-time it may be a T-view of an object of a derived type S. This means that you cannot redispatch. But this does not entitle you to call redispatching "bad" in general. > If you are in the operation inherited by S from T and you don't want it to > be T there, then why do have you inherited and not overridden it in S? > Something must be wrong in it. Not at all, it works perfectly for S. If I had overridden it for S, it would have exactly the same text as for T (except of course for the change in type name from T to S). If the operation did *not* use redispatching, it would *not* work for T as well as for S, and I would have to override it for S. > Are you saying that the reader must be constantly aware that the type of > the actual parameter could be not the type the procedure deals with? Yes, of course. Because of inheritance. >> I suggest that you show how you would design the Divide-and-Conquer >> example that I described in my reply to Randy earlier today, and then we >> can compare the two designs. > > type Subproblem is abstract ...; > procedure Conquer (X : in out Subproblem) is abstract; > type List_Of_Subproblems is ...; -- Container type of Subproblem'Class > > type Problem is abstract ...; > function Divide (X : Problem) return List_Of_Subproblems is abstract; Ok, good for discussing. This works, but has some drawbacks compared to a redispatching solution. Firstly, it needs new data structures (Subproblem and List_Of_Subproblems) which may require dynamic memory allocation if the Subproblem type is complex. Secondly, it is no longer possible to stop the Divide operation when the first solvable Subproblem is discovered. Thirdly, you now need a class-wide operation that invokes Divide and then Conquer for the given object of type T'Class. Finally, a point that is not a drawback in my view, but should be in your view (as I understand it): there is still no guarantee that the actual invoked Divide operation and the actual invoked Conquer operation are defined for the same type; the actual T'Class object might inherit Divide from type T, and Conquer from type S. Logically (but perhaps exaggerating a bit) you should consider this a type error because it is mixing the semantics of T and S. I'm sure you don't see this as a problem, probably because you assume that the semantics of T and S are sufficiently "compatible", being all in T'Class. But this is just the reason why I don't think that redispatching from Divide (T) to Conquer (S) is a type error. > A better case for re-dispatch is an operation defined in terms > of other operations. Well, I do think that Divide, in my example, is defined in terms of other operations, namely Conquer, because Divide (in my design) finds the subproblems and calls Conquer on each subproblem. Perhaps I should have called the operation Divide_And_Conquer to make this clear in the name, sorry. > For example x*n defined as x+...+x, n times. you want to have a > standard version of *, but in some cases to have an ability to > implement it specifically. I think that is quite similar to the Divide/Conquer example. > I am using a sort of this: > > procedure Add (X : in out Item; Y : Item); > procedure Mul (X : in out Item'Class; N : Positive); > private > function Has_Accelerated_Mul (X : Item) return Boolean; > procedure Accelerated_Mul (X : in out Item; N : Positive); > > procedure Mul (X : in out Item'Class; N : Positive) is > begin > if Has_Accelerated_Mul (X) then > Accelerated_Mult (X, N); Shudder (my turn :-). This is a kind of manually implemented overriding, where Accelerated_Mul sometimes "overrides" Mul. I much prefer to use the language-defined overriding of primitive operations, so I would make Mul primitive on Item, instead of class-wide, and override it with an accelerated version for those Item'Class types that allow it. This is probably faster, too :-) One problem in your design is that someone can now mistakenly call Accelerated_Mul (X) even if Has_Accelerated_Mul (X) is False. Moreover, Has_Accelerated_Mul seems to depend on the particular object (X), not just on the type (Item). Or was this your intention? > My empirical condition for all dispatching operations is that type tag > resolution should move strictly towards the root, and never backwards. I.e. > if you have a tag for T'Class,, you can narrow the set of candidates > ultimately to the single type, but you never widen the set. This rule defines a subset of Ada that you want to use. OK, but so far you have not convinced me that there is any benefit in this subset. We are all struggling to find the small nuggets of correct programs hidden in the vast rockpile of incorrect programs. I can believe that your empirical condition is helpful to you, but I have not found any problems with redispatching. I suggest that we agree to disagree and close here. -- Niklas Holsti Tidorum Ltd niklas holsti tidorum fi . @ . ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Access types as parameters 2009-08-15 19:19 ` Niklas Holsti @ 2009-08-16 8:32 ` Dmitry A. Kazakov 2009-08-16 9:52 ` Niklas Holsti 0 siblings, 1 reply; 31+ messages in thread From: Dmitry A. Kazakov @ 2009-08-16 8:32 UTC (permalink / raw) On Sat, 15 Aug 2009 22:19:41 +0300, Niklas Holsti wrote: > Dmitry A. Kazakov wrote: >> On Fri, 14 Aug 2009 19:03:52 +0300, Niklas Holsti wrote: >> >>> Of course not, only the point (within the class) of the implementation >>> of the currently executing, (possibly) inherited operation (the caller) >>> is determined. The actual type, as you well know, is any type in >>> T'Class, although it is written "T" in the operation profile. >> >> No, the actual type is T, just because the operation declaration tells so. > > This seems to be the origin of our disagreement. You want to view the > object as of type T, although at run-time it may be a T-view of an > object of a derived type S. This means that you cannot redispatch. But > this does not entitle you to call redispatching "bad" in general. Ada does not allow other cases, I mean dispatching while preserving a class-wide view on the object. What comes in mind: 1. procedure Foo (X : T'Class) is begin if X in S'Class then Bar (X); -- Now "dispatch" again elsif X in Q'Class then Baz (X); -- "dispatch" again This is a form of re-dispatch since the tag of X is analyzed twice. 2. procedure Foo (X1 : T; X2 : T'Class); called as Foo (X, X); Neither really represent a good design pattern. >> If you are in the operation inherited by S from T and you don't want it to >> be T there, then why do have you inherited and not overridden it in S? >> Something must be wrong in it. > > Not at all, it works perfectly for S. If I had overridden it for S, it > would have exactly the same text as for T (except of course for the > change in type name from T to S). That is a type error in Ada: you cannot call an operation of T on S, without a type conversion. Once you converted S to T, it is no more S. It is an ugly hack that allows you to restore S by casting it to T'Class. It is also a burden for the implementation. > If the operation did *not* use redispatching, it would *not* work for T > as well as for S, and I would have to override it for S. Yes, this is why S is not substitutable in the operations of T, you shall override it. The semantic problem is that you want to inherit "text", while substituting type annotations. This is not the model of inheritance. The proper models for this are class-wide operations and generic bodies. >> Are you saying that the reader must be constantly aware that the type of >> the actual parameter could be not the type the procedure deals with? > > Yes, of course. Because of inheritance. Shudder again. >> For example x*n defined as x+...+x, n times. you want to have a >> standard version of *, but in some cases to have an ability to >> implement it specifically. > > I think that is quite similar to the Divide/Conquer example. > >> I am using a sort of this: >> >> procedure Add (X : in out Item; Y : Item); >> procedure Mul (X : in out Item'Class; N : Positive); >> private >> function Has_Accelerated_Mul (X : Item) return Boolean; >> procedure Accelerated_Mul (X : in out Item; N : Positive); >> >> procedure Mul (X : in out Item'Class; N : Positive) is >> begin >> if Has_Accelerated_Mul (X) then >> Accelerated_Mult (X, N); > > Shudder (my turn :-). This is a kind of manually implemented overriding, > where Accelerated_Mul sometimes "overrides" Mul. I much prefer to use > the language-defined overriding of primitive operations, so I would make > Mul primitive on Item, instead of class-wide, and override it with an > accelerated version for those Item'Class types that allow it. This is > probably faster, too :-) There is no mechanism for it. Re-dispatch is a hack. What do you do is semantically not inheritance but overriding with an instance of a generic body, or some class-wide body. I think this is the key issue. Considering this semantics, there is no necessity to sink the type tag down to T only to later raise it back to S. It should dispatch to S, not to T. So re-dispatch is still a hack that indicates a language problem here. I am using another hack in comparable situations: the Rosen trick. At least it makes explicit that each object "knows" itself. > One problem in your design is that someone can now mistakenly call > Accelerated_Mul (X) even if Has_Accelerated_Mul (X) is False. Accelerated_Mul is private. But you could merge it with Has_Accelerated_Mul. > Moreover, > Has_Accelerated_Mul seems to depend on the particular object (X), not > just on the type (Item). Or was this your intention? We cannot dispatch on bare tags in Ada, so I am forced to use object where the type tag would suffice. -- Regards, Dmitry A. Kazakov http://www.dmitry-kazakov.de ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Access types as parameters 2009-08-16 8:32 ` Dmitry A. Kazakov @ 2009-08-16 9:52 ` Niklas Holsti 2009-08-16 12:38 ` Dmitry A. Kazakov 0 siblings, 1 reply; 31+ messages in thread From: Niklas Holsti @ 2009-08-16 9:52 UTC (permalink / raw) Dmitry A. Kazakov wrote: > On Sat, 15 Aug 2009 22:19:41 +0300, Niklas Holsti wrote: > >> Dmitry A. Kazakov wrote: >>> On Fri, 14 Aug 2009 19:03:52 +0300, Niklas Holsti wrote: >>> >>>> Of course not, only the point (within the class) of the implementation >>>> of the currently executing, (possibly) inherited operation (the caller) >>>> is determined. The actual type, as you well know, is any type in >>>> T'Class, although it is written "T" in the operation profile. >>> No, the actual type is T, just because the operation declaration tells so. >> This seems to be the origin of our disagreement. You want to view the >> object as of type T, although at run-time it may be a T-view of an >> object of a derived type S. This means that you cannot redispatch. But >> this does not entitle you to call redispatching "bad" in general. > > Ada does not allow other cases, I mean dispatching while preserving a > class-wide view on the object. What comes in mind: > > 1. procedure Foo (X : T'Class) is > begin > if X in S'Class then > Bar (X); -- Now "dispatch" again > elsif X in Q'Class then > Baz (X); -- "dispatch" again > > This is a form of re-dispatch since the tag of X is analyzed twice. I don't see how this relates to our subject. Yes, that code looks at the tag of X. Are you saying that any inspection of the tag of an object is "bad design", except for the case of "basic" dispatching? Although I sometimes do it, I don't much like to inspect tags, as in Foo above, because it couples the logic of a class-wide operation to the existence and properties of certain explicitly named sub-classes, which is fragile. But a redispatching call is not fragile in this way. > Re-dispatch is a hack. What do you do is > semantically not inheritance but overriding with an instance of a generic > body, or some class-wide body. I think this is the key issue. I'm sorry, I don't understand why it is "semantically not inheritance". If this is again the "type error" issue, I still don't agree with you. Your other arguments use terms and concepts that are too vague (for me) to be argued about, so I don't see how we can progress usefully. Thanks for the discussion. -- Niklas Holsti Tidorum Ltd niklas holsti tidorum fi . @ . ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Access types as parameters 2009-08-16 9:52 ` Niklas Holsti @ 2009-08-16 12:38 ` Dmitry A. Kazakov 2009-08-16 13:21 ` Niklas Holsti 0 siblings, 1 reply; 31+ messages in thread From: Dmitry A. Kazakov @ 2009-08-16 12:38 UTC (permalink / raw) On Sun, 16 Aug 2009 12:52:53 +0300, Niklas Holsti wrote: > Dmitry A. Kazakov wrote: >> On Sat, 15 Aug 2009 22:19:41 +0300, Niklas Holsti wrote: >> >>> Dmitry A. Kazakov wrote: >>>> On Fri, 14 Aug 2009 19:03:52 +0300, Niklas Holsti wrote: >>>> >>>>> Of course not, only the point (within the class) of the implementation >>>>> of the currently executing, (possibly) inherited operation (the caller) >>>>> is determined. The actual type, as you well know, is any type in >>>>> T'Class, although it is written "T" in the operation profile. >>>> No, the actual type is T, just because the operation declaration tells so. >>> This seems to be the origin of our disagreement. You want to view the >>> object as of type T, although at run-time it may be a T-view of an >>> object of a derived type S. This means that you cannot redispatch. But >>> this does not entitle you to call redispatching "bad" in general. >> >> Ada does not allow other cases, I mean dispatching while preserving a >> class-wide view on the object. What comes in mind: >> >> 1. procedure Foo (X : T'Class) is >> begin >> if X in S'Class then >> Bar (X); -- Now "dispatch" again >> elsif X in Q'Class then >> Baz (X); -- "dispatch" again >> >> This is a form of re-dispatch since the tag of X is analyzed twice. > > I don't see how this relates to our subject. Just an example of how type tag is dealt with while keeping a class-wide view of the object. Re-dispatching is no different. You publicly check the tag, declare the result of T, but keep in the sleeve S. Let's play by rules... (:-)) > Yes, that code looks at the > tag of X. Are you saying that any inspection of the tag of an object is > "bad design", except for the case of "basic" dispatching? With a high degree of probability it is. In all cases I have to check tag I feel myself guilty. > Although I sometimes do it, I don't much like to inspect tags, as in Foo > above, because it couples the logic of a class-wide operation to the > existence and properties of certain explicitly named sub-classes, which > is fragile. But a redispatching call is not fragile in this way. It is fragile because it has a behavior that is not determined by the values attributed to the type T. Its behavior is determined by the values of T'Class, which is an open-ended set. >> Re-dispatch is a hack. What do you do is >> semantically not inheritance but overriding with an instance of a generic >> body, or some class-wide body. I think this is the key issue. > > I'm sorry, I don't understand why it is "semantically not inheritance". Type S inherits F from T by composition of T.F with a type conversion: S.F = T.F o S_from_T That is the only way to define it in a typed language where S and T are distinct types. This is also how Ada defines it. Any primitive operation gets "re-declared" when you derive S from T. -- Regards, Dmitry A. Kazakov http://www.dmitry-kazakov.de ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Access types as parameters 2009-08-16 12:38 ` Dmitry A. Kazakov @ 2009-08-16 13:21 ` Niklas Holsti 2009-08-16 17:58 ` Dmitry A. Kazakov 0 siblings, 1 reply; 31+ messages in thread From: Niklas Holsti @ 2009-08-16 13:21 UTC (permalink / raw) Dmitry A. Kazakov wrote: > On Sun, 16 Aug 2009 12:52:53 +0300, Niklas Holsti wrote: > >> Are you saying that any inspection of the tag of an object is >> "bad design", except for the case of "basic" dispatching? > > With a high degree of probability it is. In all cases I have to check tag I > feel myself guilty. Me too. But it is sometimes the best solution (in Ada 95 at least), although it may cause maintenance problems. >> Although I sometimes do it, I don't much like to inspect tags, as in Foo >> above, because it couples the logic of a class-wide operation to the >> existence and properties of certain explicitly named sub-classes, which >> is fragile. But a redispatching call is not fragile in this way. > > It is fragile because it has a behavior that is not determined by the > values attributed to the type T. Its behavior is determined by the values > of T'Class, which is an open-ended set. No. That argument makes *any* class-wide operation (using dispatching) "fragile", because "its behaviour is determined by the open-ended set" of operation implementations. We must accept this kind of "fragility" if we use dispatching at all. I persist in seeing no extra fragility in redispatching. >>> Re-dispatch is a hack. What do you do is >>> semantically not inheritance but overriding with an instance of a generic >>> body, or some class-wide body. I think this is the key issue. >> I'm sorry, I don't understand why it is "semantically not inheritance". > > Type S inherits F from T by composition of T.F with a type conversion: > > S.F = T.F o S_from_T I think you have the conversion the wrong way around: If S.F should apply to an object X of type S, then X must first be converted to T, and then T.F applied: S.F = T.F o T_from_S meaning that S.F (X) = T.F (T_from_S (X)) > That is the only way to define it in a typed language where S and T are > distinct types. This is also how Ada defines it. Any primitive operation > gets "re-declared" when you derive S from T. Yes, yes, but Ada and other OO languages preserve the ability to redispatch, which does not fit into this simple mathematical functional-composition formalism. But then, we agreed that we use different formalisms, so all is OK. -- Niklas Holsti Tidorum Ltd niklas holsti tidorum fi . @ . ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Access types as parameters 2009-08-16 13:21 ` Niklas Holsti @ 2009-08-16 17:58 ` Dmitry A. Kazakov 0 siblings, 0 replies; 31+ messages in thread From: Dmitry A. Kazakov @ 2009-08-16 17:58 UTC (permalink / raw) On Sun, 16 Aug 2009 16:21:11 +0300, Niklas Holsti wrote: > Dmitry A. Kazakov wrote: >> On Sun, 16 Aug 2009 12:52:53 +0300, Niklas Holsti wrote: >> >>> Although I sometimes do it, I don't much like to inspect tags, as in Foo >>> above, because it couples the logic of a class-wide operation to the >>> existence and properties of certain explicitly named sub-classes, which >>> is fragile. But a redispatching call is not fragile in this way. >> >> It is fragile because it has a behavior that is not determined by the >> values attributed to the type T. Its behavior is determined by the values >> of T'Class, which is an open-ended set. > > No. That argument makes *any* class-wide operation (using dispatching) > "fragile", because "its behaviour is determined by the open-ended set" > of operation implementations. We must accept this kind of "fragility" if > we use dispatching at all. The fragility is not in the set size, but in the operation that handles it implicitly. It is like with side effects. You do one thing, but rely on more than that. Dispatching on the context of the type T it is fragile. On the context of T'Class it is not. >>>> Re-dispatch is a hack. What do you do is >>>> semantically not inheritance but overriding with an instance of a generic >>>> body, or some class-wide body. I think this is the key issue. >>> I'm sorry, I don't understand why it is "semantically not inheritance". >> >> Type S inherits F from T by composition of T.F with a type conversion: >> >> S.F = T.F o S_from_T > > I think you have the conversion the wrong way around: If S.F should > apply to an object X of type S, then X must first be converted to T, and > then T.F applied: > > S.F = T.F o T_from_S Yes. > meaning that > > S.F (X) = T.F (T_from_S (X)) Right. >> That is the only way to define it in a typed language where S and T are >> distinct types. This is also how Ada defines it. Any primitive operation >> gets "re-declared" when you derive S from T. > > Yes, yes, but Ada and other OO languages preserve the ability to > redispatch, which does not fit into this simple mathematical > functional-composition formalism. But then, we agreed that we use > different formalisms, so all is OK. Simple or complex I didn't see different formalisms, that don't fall apart when classes of built-in types come into consideration. Everything is reference? Is reference a reference? etc. And even if re-dispatch could be reconciled with strog typing, an aftertaste remains, to do same thing twice looks wrong. -- Regards, Dmitry A. Kazakov http://www.dmitry-kazakov.de ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Access types as parameters 2009-08-13 8:34 ` Niklas Holsti 2009-08-13 9:15 ` Dmitry A. Kazakov @ 2009-08-14 4:07 ` Randy Brukardt 2009-08-14 10:22 ` Niklas Holsti 1 sibling, 1 reply; 31+ messages in thread From: Randy Brukardt @ 2009-08-14 4:07 UTC (permalink / raw) "Niklas Holsti" <niklas.holsti@tidorum.invalid> wrote in message news:4a83d018$0$26303$4f793bc4@news.tdc.fi... > Randy Brukardt wrote: > >> You almost never want redispatching > > That's interesting as my experience is the opposite: Most of the calls > between primitive methods in my code use redispatching. > > My tagged types tend to have a hierarchy of primitive operations, for > example an Overall_Method that implements some functionality by a sequence > of calls to a Detail_Method. Some derived types override Detail_Method, so > the call from Overall_Method to Detail_Method must redispatch. Some other > (or the same) derived types override Overall_Method, so it cannot be > class-wide. It seems to me that one or the other of these routines ought to be class-wide, the fact that neither can be is a design problem. But YMMV. The primary reason that you don't (usually) want redispatch is that it is hard to guarentee the semantics of a routine if it re-dispatches and thus calls routines with unknown semantics. For Ada predefined libraries, we went so far as to create a rule that calls do *not* redispatch - A(3.1/3) (You'll have to look in the draft RM to see that one, since it was added in response to a question on Ada 2005: see http://www.adaic.com/standards/1zrm/html/RM-A.html). To quote: "For a descendant of a language-defined tagged type, the implementation shall ensure that each inherited language-defined subprogram behaves as described in this International Standard. In particular, overriding a language-defined subprogram shall not alter the effect of any inherited language-defined subprogram." In short, don't redispatch in language-defined routines. Re-dispatching is really only safe semantically if you can guarentee that the functionality of any overriding is a superset of that of the base operation. But of course there is no way to guarentee that in Ada; even if you intend it to be true and write the code with calls to the parent routine, raising an exception can cause it to become false (by skipping the parent routine). Of course, sometimes the semantics aren't very firmly defined, and redispatching works fine. As I said, YMMV. Randy. ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Access types as parameters 2009-08-14 4:07 ` Randy Brukardt @ 2009-08-14 10:22 ` Niklas Holsti 0 siblings, 0 replies; 31+ messages in thread From: Niklas Holsti @ 2009-08-14 10:22 UTC (permalink / raw) Randy Brukardt wrote: > "Niklas Holsti" <niklas.holsti@tidorum.invalid> wrote in message > news:4a83d018$0$26303$4f793bc4@news.tdc.fi... >> Randy Brukardt wrote: >> >>> You almost never want redispatching >> That's interesting as my experience is the opposite: Most of the calls >> between primitive methods in my code use redispatching. >> >> My tagged types tend to have a hierarchy of primitive operations, for >> example an Overall_Method that implements some functionality by a sequence >> of calls to a Detail_Method. Some derived types override Detail_Method, so >> the call from Overall_Method to Detail_Method must redispatch. Some other >> (or the same) derived types override Overall_Method, so it cannot be >> class-wide. > > It seems to me that one or the other of these routines ought to be > class-wide, the fact that neither can be is a design problem. But YMMV. Thanks for understanding that YMMV. Perhaps I can explain my context a bit more. Think of a class of problems that can be solved with a divide-and-conquer approach (this is not exactly my application, but it is similar). There are thus two operations: "Divide" a problem into cases, and "Conquer" a given case. There is a general, but rather slow, way to Divide any problem in the class, so the root type implements a Divide operation that applies this general procedure and then calls Conquer for each case that it generates. Some special kinds of problems (derived types and subclasses) have faster, specialized forms of Divide, so Divide is overridden for these types. Each kind of problem (derived type) has a specific Conquer method, and so each derived type overrides the Conquer operation (which is usually abstract in the root type, in fact, but that is incidental). > The primary reason that you don't (usually) want redispatch is that it is > hard to guarentee the semantics of a routine if it re-dispatches and thus > calls routines with unknown semantics. Agreed, but that argument applies as well to any kind of run-time dispatching, not just *re*-dispatching, and thus for example to dispatching calls from class-wide operations. If you have a class C with primitive operations Frobnicate and Dazzlify, which are expected to fulfil some application requirements, it is surely part of the programming task to ensure that each overriding Frobnicate or Dazzlify has the right semantics for its type, in whatever context it is called, whether using "basic" dispatching or redispatching. Assuming, of course, that the calls obey whatever preconditions are required for those operations. > For Ada predefined libraries, we went > so far as to create a rule that calls do *not* redispatch - A(3.1/3) > (You'll have to look in the draft RM to see that one, since it was > added in response to a question on Ada 2005: That question came from me, I think (15 Feb 2007). > see http://www.adaic.com/standards/1zrm/html/RM-A.html). To quote: > > "For a descendant of a language-defined tagged type, the implementation > shall ensure that each inherited language-defined subprogram behaves as > described in this International Standard. In particular, overriding a > language-defined subprogram shall not alter the effect of any inherited > language-defined subprogram." Nice to see that my question led to something, thanks. > In short, don't redispatch in language-defined routines. That was the solution I suggested in my question on Ada-Comment, as being the simplest solution. But it is not the only possible solution; another solution would have been to specify the redispatching that language-defined routines must or may do. As Tucker Taft commented, "redispatching must be seen as part of the *specification* of the primitive operation of a tagged type". Of course, in my own code I carefully specify which operations use redispatching :-) > Re-dispatching is really only safe semantically if you can guarentee that > the functionality of any overriding is a superset of that of the base > operation. But of course there is no way to guarentee that in Ada; Unfortunately, Ada does not guarantee that you write a correct program, period. (But I do find that she helps.) > Of course, sometimes the semantics aren't very firmly defined, and > redispatching works fine. I would say, rather, that redispatching works fine *if* the semantics of the primitive operations *are* firmly defined, with redispatching in mind. -- Niklas Holsti Tidorum Ltd niklas holsti tidorum fi . @ . ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Access types as parameters 2009-08-13 1:06 ` Randy Brukardt 2009-08-13 8:34 ` Niklas Holsti @ 2009-08-18 12:22 ` Stephen Leake 1 sibling, 0 replies; 31+ messages in thread From: Stephen Leake @ 2009-08-18 12:22 UTC (permalink / raw) "Randy Brukardt" <randy@rrsoftware.com> writes: > "Stephen Leake" <stephen_leake@stephe-leake.org> wrote in message > news:uprb1epp8.fsf@stephe-leake.org... > >> To be convincing, I'd have to post an example from Gtk or OpenToken >> written both ways; maybe later I'll find time for that. > > Yeah, I know that we are both talking rather hypothetically, and we may have > rather different examples in mind. I've now written a small example of the two styles; see http://www.stephe-leake.org/ada/access_vs_object.html The problem domain of this example is grammars; the goal of the application is to allow the user to build a data structure that represents a grammar, so the application can then use that structure to parse input strings. Typical grammars have recursion in them. This requires pointers of some form in the data structure. The access_style package tree in this example uses explicit access types as the pointers, and access parameters in the relevant grammar constructing functions. The object_style package tree uses Ada.Containers Cursors as the pointers. The object style contains a significant kludge; there is a global (but private, in Object_Style.Pointers spec) List object that holds all the tokens needed for the grammar. That was the only way I could see to get a Cursor for each. AI05-0069-1 (http://www.ada-auth.org/cgi-bin/cvsweb.cgi/ai05s/ai05-0069-1.txt) defines holder containers that should eliminate this kludge in Ada 201z. In addition, in Object_Style.Sequence "&" body, we must examine the tag of the arguments, rather than using dispatching to achieve the same result. This doesn't happen in the access style, because we can define two "&" functions with different type profiles; one takes a token object, the other an access to class-wide. Finally, some type information is lost with this style. Compare object_style-run.adb to access_style-run.adb; there are more precise types in the access style version. In the full OpenToken application, this becomes even more significant. There are two main objections to the access style: 1) Users must manage memory 2) If users pass in access values with nested accessibility, they will get Program_Error at run-time. The first objection can be mitigated by realizing that there is never any need (in this application) to deallocate any of the grammar structures. This could be made explicit with a new pragma No_Deallocation, applied to the access types. The second objection can be eliminated by adding preconditions to the subprograms that take access parameters, as defined by ai05-0145-1 and ai05-0024-1: pragma Inherited_Precondition ("&", Left in Access_Style.Token_Access_Type); A reasonably smart compiler can then report the accessibility errors at compile time rather than runtime. A third style would be a modification of the access style; define the access type storage_size to be zero, so that no allocations (or deallocations) can be done. This forces the user to define aliased grammar objects on the stack at library level, and then reference them in other grammar objects via 'access. That is significantly more awkward than allowing allocation, but not deallocation. With the facilities of Ada 201z, I believe using explicit access types and access parameters is the best fit to the problem domain. Using container Cursors as the pointers is awkward, loses type information, and doesn't gain enough to be worth it. This thread originally started discussing Gtk, which also uses access parameters. In my (few, small) Gtk applications, I don't deallocate Gtk widgets; I create them all at the start, and then show and hide them as appropriate. So I could live with a Gtk that had pragma No_Deallocations on the access types. Other people might object to that, though :). -- -- Stephe ^ permalink raw reply [flat|nested] 31+ messages in thread
end of thread, other threads:[~2009-08-18 12:22 UTC | newest] Thread overview: 31+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2009-07-17 8:39 Access types as parameters Rick 2009-07-17 15:03 ` Adam Beneschan 2009-07-17 16:28 ` Hibou57 (Yannick Duchêne) 2009-07-17 23:25 ` rickduley 2009-07-18 1:03 ` Randy Brukardt 2009-07-19 22:57 ` rickduley 2009-07-20 0:10 ` John B. Matthews 2009-07-20 8:13 ` Dmitry A. Kazakov 2009-07-21 0:34 ` Randy Brukardt 2009-07-21 14:34 ` Adam Beneschan 2009-07-23 2:11 ` Stephen Leake 2009-08-11 23:41 ` Randy Brukardt 2009-08-12 2:22 ` Stephen Leake 2009-08-13 1:06 ` Randy Brukardt 2009-08-13 8:34 ` Niklas Holsti 2009-08-13 9:15 ` Dmitry A. Kazakov 2009-08-13 20:13 ` Niklas Holsti 2009-08-13 21:07 ` Dmitry A. Kazakov 2009-08-14 9:27 ` Niklas Holsti 2009-08-14 10:36 ` Dmitry A. Kazakov 2009-08-14 16:03 ` Niklas Holsti 2009-08-15 9:47 ` Dmitry A. Kazakov 2009-08-15 19:19 ` Niklas Holsti 2009-08-16 8:32 ` Dmitry A. Kazakov 2009-08-16 9:52 ` Niklas Holsti 2009-08-16 12:38 ` Dmitry A. Kazakov 2009-08-16 13:21 ` Niklas Holsti 2009-08-16 17:58 ` Dmitry A. Kazakov 2009-08-14 4:07 ` Randy Brukardt 2009-08-14 10:22 ` Niklas Holsti 2009-08-18 12:22 ` Stephen Leake
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox