From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on polar.synack.me X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00 autolearn=ham autolearn_force=no version=3.4.4 X-Google-Thread: a07f3367d7,7ff1de84a8945e80 X-Google-Attributes: gida07f3367d7,public,usenet X-Google-NewGroupId: yes X-Google-Language: ENGLISH,ASCII-7-bit Path: g2news2.google.com!news3.google.com!feeder.news-service.com!news.astraweb.com!border5.a.newsrouter.astraweb.com!feeder3.cambriumusenet.nl!feed.tweaknews.nl!138.195.8.3.MISMATCH!news.ecp.fr!news.jacob-sparre.dk!pnx.dk!not-for-mail From: "Randy Brukardt" Newsgroups: comp.lang.ada Subject: Re: Access types as parameters Date: Wed, 12 Aug 2009 20:06:23 -0500 Organization: Jacob Sparre Andersen Message-ID: References: <521c4843-d40f-4545-9e80-ca725e847090@h21g2000yqa.googlegroups.com> <8410fc60-9b8a-4f82-92fc-622a6bbe5931@i18g2000pro.googlegroups.com> <8880c3d0-a07f-4d4e-ac87-372014598576@d15g2000prc.googlegroups.com> NNTP-Posting-Host: static-69-95-181-76.mad.choiceone.net X-Trace: munin.nbi.dk 1250125650 17785 69.95.181.76 (13 Aug 2009 01:07:30 GMT) X-Complaints-To: news@jacob-sparre.dk NNTP-Posting-Date: Thu, 13 Aug 2009 01:07:30 +0000 (UTC) X-Priority: 3 X-MSMail-Priority: Normal X-Newsreader: Microsoft Outlook Express 6.00.2900.5512 X-RFC2646: Format=Flowed; Original X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.5579 Xref: g2news2.google.com comp.lang.ada:7708 Date: 2009-08-12T20:06:23-05:00 List-Id: "Stephen Leake" wrote in message news:uprb1epp8.fsf@stephe-leake.org... > "Randy Brukardt" writes: >> "Stephen Leake" 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.