|
Prev: Robert Dewar's great article about the Strengths of Ada overother langauges in multiprocessing!
Next: Converting Type Characters to type string
From: Maciej Sobczak on 31 Mar 2008 16:22 Consider a generic subprogram that makes sense for arguments of both class-wide type and a concrete type. As a motivating example, there might be a hierarchy of iterator types rooted in some imaginary Iterator type that is itself defined in the generic package with the element type as its own formal parameter. There are concrete iterator types that are derived from this root Iterator type (exactly: from some instantiation thereof). There might be also a generic subprogram that operates on the iterator given as its parameter. This subprogram takes two formal generic parameters: the element type and the iterator type. Now - on one hand it makes sense to have a hierarchy of iterators and benefit from loose coupling and other features of OO, but on the other hand the iterators themselves can be lightweight objects that are used in tight loops and we can expect them to be fast, therefore we could benefit from *avoiding* the dynamic dispatch if there is enough context to do so. Both make sense, depending on the context at the call site. To achieve both benefits the user might instantiate the subprogram for the Iterator'Class type (to be exact: for the 'Class of some instantiation of Iterator) in the context where only a class-wide type is available, like within some other polymorphic subprogram; and for the concrete type, like My_Concrete_Iterator, in the context where the concrete type is available, with the hope that such an instantiation can be more easily inlined. The sketch of types involved is: generic type T is private; -- element type package Iterators is type Iterator is interface; function Get (I : Iterator) return T is abstract; -- and so on for other operations... end Iterators; and at the user side (let's suppose the user works with Integers only): package body My_Stuff is package Iterators_Integer is new Iterators (T => Integer); type My_Concrete_Iterator is new Iterators_Integer.Iterator with ... overriding function Get (I : My_Concrete_Iterator) return Integer; -- and so on for other operations... end My_Stuff; Now, the generic subprogram that operates on the given iterator, *without* using dynamic dispatch, can have the following form: generic type Element is private; type Iterator_Type (<>) is private; with function Get (I : Iterator_Type) return Element is <>; -- and so on for all other operations that are needed by this subprogram procedure Some_Procedure (I : in Iterator_Type); This works fine for direct instantiation with My_Concrete_Iterator: procedure SP is new Some_Procedure (T => Integer, Iterator_Type => My_Concrete_Iterator); The problem is that I failed to instantiate Some_Procedure for Iterator_Integer.Iterator'Class, which I could then reuse for My_Concrete_Iterator as well as for My_Other_Concrete_Iterator and so on: -- does not compile: procedure SP is new Some_Procedure (T => Integer, Iterator_Type => Iterator_Integer'Class); -- Bang! Bang, because relevant iterator operations cannot be found - the ones that are found have *wrong signatures*. Separate version of Some_Procedure can be written for polymorphic operations: generic type Element is private; with package Its is new Iterators (T => Element); -- no need to enumerate any operations, the interface serves its purpose procedure Some_Procedure (I : in Its.Iterator'Class); This works fine when some instantiation of base Iterator type is given: procedure SP is new Some_Procedure (T => Integer, Its => Iterators_Integer); and then SP can be used with My_Concrete_Iterator, presumably dispatching on all calls to iterator in its body. My expectation is that there should be a way to implement only one generic procedure that can be instantiated for both concrete types and for 'Class type. Otherwise, I would need to provide both versions of Some_Procedure, which not only looks like unnecessary duplication of code (the implementations would be even textually identical!), but also seems to be impossible - for some reason these two versions cannot exist in the same package. What the heck? I did not expect two orthogonal language features (generics and OO) to interact in such a way. Is there a good solution to this problem? -- Maciej Sobczak * www.msobczak.com * www.inspirel.com
From: Adam Beneschan on 31 Mar 2008 19:38 On Mar 31, 1:22 pm, Maciej Sobczak <see.my.homep...(a)gmail.com> wrote: > Consider a generic subprogram that makes sense for arguments of both > class-wide type and a concrete type. [snip] > Now, the generic subprogram that operates on the given iterator, > *without* using dynamic dispatch, can have the following form: > > generic > type Element is private; > type Iterator_Type (<>) is private; > with function Get (I : Iterator_Type) return Element is <>; > -- and so on for all other operations that are needed by this > subprogram > procedure Some_Procedure (I : in Iterator_Type); > > This works fine for direct instantiation with My_Concrete_Iterator: > > procedure SP is new Some_Procedure > (T => Integer, Iterator_Type => My_Concrete_Iterator); > > The problem is that I failed to instantiate Some_Procedure for > Iterator_Integer.Iterator'Class, which I could then reuse for > My_Concrete_Iterator as well as for My_Other_Concrete_Iterator and so > on: > > -- does not compile: > procedure SP is new Some_Procedure > (T => Integer, Iterator_Type => Iterator_Integer'Class); -- Bang! > > Bang, because relevant iterator operations cannot be found - the ones > that are found have *wrong signatures*. [snip] At first, I thought it might be possible to do this, without duplicating code, by writing a second generic package specifically for class-wide types that would instantiate Some_Procedure. Something like this: generic type Element is private; type Iterator_Root is tagged private; with function Get (I : in Iterator_Root) return Element is <>; ... other operations package SP_For_Class is procedure Some_Procedure_Class (I : in Iterator_Root'Class); end SP_For_Class; with Some_Procedure; package body SP_For_Class is function Dispatching_Get (I : in Iterator_Root'Class) return Element is begin return Get (I); end Dispatching_Get; ... similarly for other operations procedure SP_Inst is new Some_Procedure (Element, Iterator_Root'Class, Dispatching_Get, ...other operations); procedure Some_Procedure_Class (I : in Iterator_Root'Class) renames SP_Inst; end SP_For_Class; Then for a concrete type, the programmer would instantiate Some_Procedure; for a class-wide type, the programmer would instantiate SP_For_Class, and then if Inst is the instance, Inst.Some_Procedure_Class would be the equivalent procedure. SP_For_Class would be sort of a wrapper, but it wouldn't need to duplicate any of the logic from Some_Procedure. The problem I ran into was the line "return Get(I)" fails because Get is not a primitive operation of Iterator_Root and thus is not seen as a dispatching operation. What's missing here is a way to specify a generic formal subprogram that must be a primitive operation of some tagged type (possibly a generic formal tagged type), so that in the body of the generic the formal subprogram will be treated as a dispatching operation; in an instantiation, of course, the actual subprogram would have to meet the criterion. It seems like this might be a useful feature in some cases besides this one (although I can't think of one offhand), but I can see how it would be difficult to work this into the syntax. (Especially if the generic formal subprogram has two different parameter or result tagged types; you'd need a way to tell it which type the subprogram must be a primitive operation of.) (P.S. It seems like we had a discussion on Ada-Comment some years ago about the sort of issue Maciej mentions, at least with regard to the "=" function. Some programmers were writing code, which GNAT accepted due a bug, where generics declared with function "=" (Left, Right : T) is <>; and then the generic was instantiated with some class-wide type for T. It seemed like there was some sympathy for allowing this or providing for a capability that would make this work, but not enough sympathy for anyone to actually do anything about it. Bob Duff, does this ring a bell at all?) -- Adam
From: Randy Brukardt on 31 Mar 2008 20:23 "Maciej Sobczak" <see.my.homepage(a)gmail.com> wrote in message news:279b6f4f-36cf-446f-8b54-fd72b957b22f(a)i7g2000prf.googlegroups.com... > Consider a generic subprogram that makes sense for arguments of both > class-wide type and a concrete type. .... > The problem is that I failed to instantiate Some_Procedure for > Iterator_Integer.Iterator'Class, which I could then reuse for > My_Concrete_Iterator as well as for My_Other_Concrete_Iterator and so > on: > > -- does not compile: > procedure SP is new Some_Procedure > (T => Integer, Iterator_Type => Iterator_Integer'Class); -- Bang! > > Bang, because relevant iterator operations cannot be found - the ones > that are found have *wrong signatures*. Right. This seems related to the problem mentioned in AI05-0071-1. (That AI is still under construction, but it also is about operations having the wrong signatures.) Since I'm not sure how that AI is going to be fixed, it's not clear to me if it will cover this case or not. But perhaps you should submit a question to Ada Comment so that it gets considered as well. Randy.
From: Eric Hughes on 31 Mar 2008 23:57 "Maciej Sobczak" <see.my.homep...(a)gmail.com> wrote in message > Bang, because relevant iterator operations cannot be found - the ones > that are found have *wrong signatures*. On Mar 31, 6:23 pm, "Randy Brukardt" <ra...(a)rrsoftware.com> wrote: > Right. This seems related to the problem mentioned in AI05-0071-1. To my eye, it's also related to the discussion I was in recently involving generic parameters, where the issue was hypothetical, but also involved symbol mappings. I've thought a lot about this thread, and rather than start with a long discussion, I think a small riddle is in order, one which seems to cut to the center of the problem. Is it possible to get both dispatching and non-dispatching calls on a single type (expressed as a single formal parameter) within a generic? Eric
From: christoph.grein on 1 Apr 2008 02:58
This works: package Classwide is function Get (Iterator: My_Stuff.My_Iterator'Class) return Integer; procedure Classwide_Procedure is new Some_Procedure (Element => Integer, Iterator_Type => My_Stuff.My_Iterator'Class, Get => Get); end Classwide; package body Classwide is function Get (Iterator: My_Stuff.My_Iterator'Class) return Integer is begin return My_Stuff.Get (Iterator); end Get; end Classwide; |