comp.lang.ada
 help / color / mirror / Atom feed
From: "Randy Brukardt" <randy@rrsoftware.com>
Subject: Re: Access types as parameters
Date: Wed, 12 Aug 2009 20:06:23 -0500
Date: 2009-08-12T20:06:23-05:00	[thread overview]
Message-ID: <h5vp0i$hbp$1@munin.nbi.dk> (raw)
In-Reply-To: uprb1epp8.fsf@stephe-leake.org

"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.





  reply	other threads:[~2009-08-13  1:06 UTC|newest]

Thread overview: 31+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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 [this message]
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
replies disabled

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox