Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Calling inherited primitive operations in Ada

187 views
Skip to first unread message

Emmanuel Briot

unread,
Aug 31, 2022, 4:15:40 AM8/31/22
to
A small blog post that you might find interesting:

https://deepbluecap.com/calling-inherited-primitive-operations-in-ada/

We are preparing a number of other posts, but right now this is the only one on our web site, so the links are a bit hidden. They will become available on https://deepbluecap.com/blog/ as we publish them.

Emmanuel

DrPi

unread,
Aug 31, 2022, 1:11:24 PM8/31/22
to
That's interesting.

You should compile all excerpts of code you publish. This will avoid
typos like "Polgyon" ;)

Nicolas

Dmitry A. Kazakov

unread,
Aug 31, 2022, 3:13:18 PM8/31/22
to
On 2022-08-31 10:15, Emmanuel Briot wrote:
> A small blog post that you might find interesting:
>
> https://deepbluecap.com/calling-inherited-primitive-operations-in-ada/

This same technique is used in generics to work around another language
design "feature":

generic
type Foo is ...;
package
subtype Actual_Foo is Foo;

--
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de

Emmanuel Briot

unread,
Sep 1, 2022, 2:56:28 AM9/1/22
to
> generic
> type Foo is ...;
> package
> subtype Actual_Foo is Foo;

To me, this is an orthogonal issue though (which would be worth its own blog post in fact). I can never remember (or perhaps not even understand) the reason for this limitation in Ada, which is a major pain when dealing with generics indeed...
I like the "Actual_" prefix, which I assume is some sort of convention in your code.

Emmanuel

amo...@unizar.es

unread,
Sep 1, 2022, 3:57:35 AM9/1/22
to
Is this about how according to some mystifying rules generic formals are[n't] visible from outside the generic?

Dmitry A. Kazakov

unread,
Sep 1, 2022, 6:02:46 AM9/1/22
to
Right. I do not remember the rules, just the fact that they are quite
logical. Unfortunately the logic of is not very helpful. (:-))

As for primitive operations the problems are on many levels, from
lacking introspection to missing inheritance of implementation by
composition (AKA hooking).

Jeffrey R.Carter

unread,
Sep 1, 2022, 7:59:32 AM9/1/22
to
On 2022-09-01 09:57, amo...@unizar.es wrote:
>
> Is this about how according to some mystifying rules generic formals are[n't] visible from outside the generic?

This seems like a non-issue to me. Any code that has visibility to a generic
instance knows the actuals used for that instance. Can anyone provide real
examples where this is a problem?

--
Jeff Carter
"[M]any were collected near them, ... to
enjoy the sight of a dead young lady, nay,
two dead young ladies, for it proved twice
as fine as the first report."
Persuasion
155

Dmitry A. Kazakov

unread,
Sep 1, 2022, 8:37:39 AM9/1/22
to
On 2022-09-01 13:59, Jeffrey R.Carter wrote:
> On 2022-09-01 09:57, amo...@unizar.es wrote:
>>
>> Is this about how according to some mystifying rules generic formals
>> are[n't] visible from outside the generic?
>
> This seems like a non-issue to me. Any code that has visibility to a
> generic instance knows the actuals used for that instance.

That would make the code fragile. Should be avoided as much as possible
as a form of aliasing. Compare:

procedure A (X : Integer) is
procedure B (Y : Integer) is
begin
X := 1; -- We know we are going to call it with X!
end B;
begin
B (X);
end A;

> Can anyone
> provide real examples where this is a problem?

Defaulted formal package actual part:

generic
package Foo is new Bar (<>);
package Baz is ...
-- What were these actuals in Foo?

This is one of most useful features used to reduce lists of formal
parameters and simplify instantiations.

Jeffrey R.Carter

unread,
Sep 1, 2022, 9:37:15 AM9/1/22
to
On 2022-09-01 14:37, Dmitry A. Kazakov wrote:
>

Toy problem ignored.

>> Can anyone provide real examples where this is a problem?
>
> Defaulted formal package actual part:
>
>    generic
>       package Foo is new Bar (<>);
>    package Baz is ...
>       -- What were these actuals in Foo?

1. The actuals of Foo are visible within Baz.
2. This is not a real example.
3. You have changed the subject from needing to rename a formal type. I presume
this is because there are no real examples where renaming a formal type in an
instance is needed.

Emmanuel Briot

unread,
Sep 1, 2022, 10:11:54 AM9/1/22
to
I have seen quite a number of cases of needing the subtype like Dmitry was showing. In a small number of cases, those were actual bugs in GNAT, but most of the time the compiler was correct.
Mostly, you start to see the issue when you have generic packages that have formal generic packages.

Here is a quick example and the corresponding compiler error message. In Main, there is no way to see T. Of course, I can use Integer_Signature
directly, but this is an issue.
- if I rename Integer_Signature then I have to change a lot of places in my code (The aliasing that Dmitry was talking about)

with Signature;
generic
with package Sign is new Signature (<>);
package Algo is
procedure Compute (V : Sign.T) is null;
end Algo;

with Algo;
with Signature;
package Lib is
package Integer_Signature is new Signature (Integer);
package Integer_Algo is new Algo (Integer_Signature);
end Lib;


with Lib;
procedure Main is
V : Lib.Integer_Algo.Sign.T;
-- main.adb:3:24: "Sign" is not a visible entity of "Integer_Algo"
begin
null;
end Main;

generic
type T is private;
package Signature is
end Signature;

There are more interesting examples, somehow this one doesn't seem that bad. So here is another one:

generic
type T is private;
package Gen is
end Gen;

with Gen;
generic
type T is private;
with package Must_Match is new Gen (T);
with package Need_Not_Match is new Gen (<>);
package Gen2 is
V1 : Must_Match.T; -- "T" is not a visible entity of "Must_Match"
V2 : Need_Not_Match.T; -- instance of same package, but this time T is visible
end Gen2;

with Gen, Gen2;
procedure P2 is
package G is new Gen (Integer);
package G2 is new Gen2 (Integer, G, G);
begin
null;
end P2;




I dug out the explanation that Tucker Taft once sent to the Ada-Comment mailing list (2019-11-14):

<<<
10/2
{AI95-00317-01} The visible part of a formal package includes the first list of basic_declarative_items of the package_specification. In addition, for each actual parameter that is not required to match, a copy of the declaration of the corresponding formal parameter of the template is included in the visible part of the formal package. If the copied declaration is for a formal type, copies of the implicit declarations of the primitive subprograms of the formal type are also included in the visible part of the formal package.
10.a/2
Ramification: {AI95-00317-01} If the formal_package_actual_part is (<>), then the declarations that occur immediately within the generic_formal_part of the template for the formal package are visible outside the formal package, and can be denoted by expanded names outside the formal package.If only some of the actual parameters are given by <>, then the declaration corresponding to those parameters (but not the others) are made visible.
10.b/3
Reason: {AI05-0005-1} We always want either the actuals or the formals of an instance to be nameable from outside, but never both. If both were nameable, one would get some funny anomalies since they denote the same entity, but, in the case of types at least, they might have different and inconsistent sets of primitive operators due to predefined operator “reemergence.” Formal derived types exacerbate the difference. We want the implicit declarations of the generic_formal_part as well as the explicit declarations, so we get operations on the formal types.
>>>

amo...@unizar.es

unread,
Sep 1, 2022, 11:50:23 AM9/1/22
to
On Thursday, September 1, 2022 at 4:11:54 PM UTC+2, briot.e...@gmail.com wrote:
> I have seen quite a number of cases of needing the subtype like Dmitry was showing. In a small number of cases, those were actual bugs in GNAT, but most of the time the compiler was correct.
> Mostly, you start to see the issue when you have generic packages that have formal generic packages.

This matches exactly my experience. I don't have enough grasp of the details to come up with a realistic short example, but I did hit this issue pretty often in two libs were I used signature packages quite extensively:

https://github.com/mosteo/rxada
https://github.com/mosteo/iterators

Initially I was always under the impression I was hitting GNAT bugs but then it turned out there were rules about it. A couple example places (you can see the renamings at the top. I was adding them "on demand" so to say):

https://github.com/mosteo/iterators/blob/master/src/iterators-traits-containers.ads
https://github.com/mosteo/rxada/blob/master/src/priv/rx-impl-transformers.ads

Thanks Emmanuel for the examples and digging out Tucker explanation.

-Alex.

Jeffrey R.Carter

unread,
Sep 1, 2022, 12:03:22 PM9/1/22
to
On 2022-09-01 16:10, Emmanuel Briot wrote:
> I have seen quite a number of cases of needing the subtype like Dmitry was showing. In a small number of cases, those were actual bugs in GNAT, but most of the time the compiler was correct.
> Mostly, you start to see the issue when you have generic packages that have formal generic packages.

None of these deal with the example I responded to

generic
type T is ...
package P is
subtype Actual_T is T;

> Reason: {AI05-0005-1} We always want either the actuals or the formals of an instance to be nameable from outside, but never both.
This is true in all these examples. I have used Ada since 1984, and this has
never been a problem for me (as initially presented, this would have existed in
Ada 83). Of course, I generally avoid generic formal pkgs. They seem to me to be
a work around for poor design, and I prefer to correct the design.

Emmanuel Briot

unread,
Sep 1, 2022, 12:07:12 PM9/1/22
to
On Thursday, September 1, 2022 at 6:03:22 PM UTC+2, Jeffrey R.Carter wrote:
> This is true in all these examples. I have used Ada since 1984, and this has
> never been a problem for me (as initially presented, this would have existed in

So basically saying that you do not use generics (or only very basic versions) and you have never seen
the bug that relates to complex generics. Not very useful input ?
For sure, that's already a number of persons who have said they have seen this bug a number of times.
I have of course only been using Ada for 25 years, but that sure was enough to see that issue...

Jeffrey R.Carter

unread,
Sep 1, 2022, 12:17:09 PM9/1/22
to
On 2022-09-01 18:07, Emmanuel Briot wrote:
>
> So basically saying that you do not use generics (or only very basic versions) and you have never seen
> the bug that relates to complex generics. Not very useful input ?

I have not said this. I have worked on very large problems and on generics
appropriate to such problems. Of course, KISS is an important S/W-eng principle.

Emmanuel Briot

unread,
Sep 1, 2022, 2:54:02 PM9/1/22
to
I think I have a more interesting example. This one is extracted from my attempted traits containers, for which I had published a blog post at AdaCore. My enhanced fork of the library is at
https://github.com/briot/ada-traits-containers
if someone wants to experiment with non-trivial generics code (and containers are of course a case where generics fully make sense).

Here is the example:

generic
type Element_Type is private;
type Stored_Type is private;
package Elements is
end Elements;

with Elements;
generic
type Element_Type is private;
package Definite_Elements is
package Traits is new Elements (Element_Type, Stored_Type => Element_Type);
end Definite_Elements;

with Definite_Elements;
generic
type Key_Type is private;
package Maps is
package Keys is new Definite_Elements (Key_Type);
function "=" (L, R : Keys.Traits.Stored_Type) return Boolean -- "Stored_Type" is not a visible entity of "Traits"
is (False);
end Maps;


This is not case where the actual is visible unless I happen to know how Definite_Element is implemented and that it will use Element_Type for Stored_Type (and this is not a knowledge I wish client packages to have, the whole point of Element and Definite_Element is to basically hide how elements can be stored in a container, and whether we need memory allocation for instance).

Jeffrey R.Carter

unread,
Sep 1, 2022, 5:33:47 PM9/1/22
to
On 2022-09-01 20:54, Emmanuel Briot wrote:
>
> generic
> type Element_Type is private;
> type Stored_Type is private;
> package Elements is
> end Elements;
>
> with Elements;
> generic
> type Element_Type is private;
> package Definite_Elements is
> package Traits is new Elements (Element_Type, Stored_Type => Element_Type);
> end Definite_Elements;
>
> with Definite_Elements;
> generic
> type Key_Type is private;
> package Maps is
> package Keys is new Definite_Elements (Key_Type);
> function "=" (L, R : Keys.Traits.Stored_Type) return Boolean -- "Stored_Type" is not a visible entity of "Traits"
> is (False);
> end Maps;

As presented, this seems very over complicated. Elements and Definite_Elements
add no value, and this is just a complex way of writing

generic
type Key is private;
package Maps is
function "=" (L : in Key; R : in Key) return Boolean is (False);
end Maps;

One hopes that the actual library makes better use of these pkgs than this small
example.

> This is not case where the actual is visible unless I happen to know how Definite_Element is implemented and that it will use Element_Type for Stored_Type (and this is not a knowledge I wish client packages to have, the whole point of Element and Definite_Element is to basically hide how elements can be stored in a container, and whether we need memory allocation for instance).

Ada has excellent features for hiding, but these are not among them. Assuming
that pkg Keys serves some actual role in the spec of Maps, then someone wanting
to use pkg Maps will need to understand that role, which involves understanding
Definite_Elements, which involves understanding pkg Traits, which involves
understanding Elements. Rather than hiding anything, you are effectively
requiring your users to look at these things, even if you could use the notation
in question.

Emmanuel Briot

unread,
Sep 2, 2022, 2:11:47 AM9/2/22
to
> As presented, this seems very over complicated. Elements and Definite_Elements
> add no value, and this is just a complex way of writing

I did point you to the full repository if you prefer an extensive, real-life code sample. This was just an extract showing the gist of the issue.

> One hopes that the actual library makes better use of these pkgs than this small
> example.

One can check the full code and extensive documentation on the design in the github repository.
This is called code reuse, was invented quite a while ago.
These packages are mostly implementation details. They are used to build high-level packages similar to the Ada containers, except with much better code reuse, more efficient, and SPARK-provable.

> Ada has excellent features for hiding, but these are not among them. Assuming

And that's exactly our point in this discussion. Ada on the whole is very good (we would not be using it otherwise), but it does have a number of limitations which are sometimes a pain, this being one of them. Not acknowledging the limitations when they exist is naive, all languages have them.

> that pkg Keys serves some actual role in the spec of Maps, then someone wanting
> to use pkg Maps will need to understand that role, which involves understanding
> Definite_Elements, which involves understanding pkg Traits, which involves
> understanding Elements.

which involves understanding Ada, which involves understanding the compiler, which involves understanding the kernel sources, ...
How many times have you checked the whole sources of the GNAT runtime to understand how they implemented the Ada containers ?

Emmanuel

amo...@unizar.es

unread,
Sep 2, 2022, 4:35:13 AM9/2/22
to
On Thursday, September 1, 2022 at 11:33:47 PM UTC+2, Jeffrey R.Carter wrote:
> On 2022-09-01 20:54, Emmanuel Briot wrote:

> As presented, this seems very over complicated. Elements and Definite_Elements
> add no value, and this is just a complex way of writing

Going in a tangent, and I guess you know perfectly well, but this is caused by the painful duplication of code that Ada pushes you to by not having a native way to abstract storage of definite vs indefinite types. So the provider of a generic library very soon faces this conundrum about duplicating most interfaces, if not implementations, or resort to non-trivial generics, or accept an unnecessary penalty for definite types, or push to the client the definite storage matter. There's simply no satisfying solution here [that I know of]. The duplication of every standard container to have both [in]definite variants is a strong indictment.

I can understand the desire to have full control of allocation and object sizes, but that there's not a language way to work around this duplication, with appropriate restrictions to go with it, is... bothersome. Not making a dig at the ARG, which I understand is overstretched as it is.

There was a proposal circulating some time ago that seemed promising, that I can't quickly find. Something like

type Blah is record
Dont_care_if_in_heap: new Whatever_Definiteness; -- Would apply to indefinite types or formals
end record;

I don't think it made into https://github.com/AdaCore/ada-spark-rfcs/ or https://github.com/Ada-Rapporteur-Group/User-Community-Input/issues or I can't find it.

Dmitry A. Kazakov

unread,
Sep 2, 2022, 4:48:57 AM9/2/22
to
On 2022-09-02 10:35, amo...@unizar.es wrote:
> On Thursday, September 1, 2022 at 11:33:47 PM UTC+2, Jeffrey R.Carter wrote:
>> On 2022-09-01 20:54, Emmanuel Briot wrote:
>
>> As presented, this seems very over complicated. Elements and Definite_Elements
>> add no value, and this is just a complex way of writing
>
> Going in a tangent, and I guess you know perfectly well, but this is caused by the painful duplication of code that Ada pushes you to by not having a native way to abstract storage of definite vs indefinite types. So the provider of a generic library very soon faces this conundrum about duplicating most interfaces, if not implementations, or resort to non-trivial generics, or accept an unnecessary penalty for definite types, or push to the client the definite storage matter. There's simply no satisfying solution here [that I know of]. The duplication of every standard container to have both [in]definite variants is a strong indictment.
>
> I can understand the desire to have full control of allocation and object sizes, but that there's not a language way to work around this duplication, with appropriate restrictions to go with it, is... bothersome. Not making a dig at the ARG, which I understand is overstretched as it is.

Containers should be implementable without generics. Just saying.

> There was a proposal circulating some time ago that seemed promising, that I can't quickly find. Something like
>
> type Blah is record
> Dont_care_if_in_heap: new Whatever_Definiteness; -- Would apply to indefinite types or formals
> end record;

I would prefer constraint propagation/management support + tuples instead:

type Blah (Parameters : Whatever_Definiteness'Constraints) is
Sill_Care : Whatever_Definiteness (Parameters);
end record;

amo...@unizar.es

unread,
Sep 2, 2022, 5:20:46 AM9/2/22
to
On Friday, September 2, 2022 at 10:48:57 AM UTC+2, Dmitry A. Kazakov wrote:

> Containers should be implementable without generics. Just saying.

Are you now referring to current Ada or to hypothetical features?

> I would prefer constraint propagation/management support + tuples instead:
>
> type Blah (Parameters : Whatever_Definiteness'Constraints) is
> Sill_Care : Whatever_Definiteness (Parameters);
> end record;

Whatever ;-) as long as it's an effective solution.

Dmitry A. Kazakov

unread,
Sep 2, 2022, 5:55:15 AM9/2/22
to
On 2022-09-02 11:20, amo...@unizar.es wrote:
> On Friday, September 2, 2022 at 10:48:57 AM UTC+2, Dmitry A. Kazakov wrote:
>
>> Containers should be implementable without generics. Just saying.
>
> Are you now referring to current Ada or to hypothetical features?

Hypothetical like all types having classes and supertypes. E.g. when you
instantiate generic with a type you semantically place the type in the
implicit class of formal types of the generic. You cannot do that now
without generics. Furthermore, there is no way to describe relationships
between types like array index and array, like range and discrete type
of its elements etc.

Jeffrey R.Carter

unread,
Sep 2, 2022, 6:41:45 AM9/2/22
to
On 2022-09-02 10:35, amo...@unizar.es wrote:
>
> Going in a tangent, and I guess you know perfectly well, but this is caused by the painful duplication of code that Ada pushes you to by not having a native way to abstract storage of definite vs indefinite types. So the provider of a generic library very soon faces this conundrum about duplicating most interfaces, if not implementations, or resort to non-trivial generics, or accept an unnecessary penalty for definite types, or push to the client the definite storage matter. There's simply no satisfying solution here [that I know of]. The duplication of every standard container to have both [in]definite variants is a strong indictment.

The only indefinite data structure that is needed seems to be holders. Any other
indefinite data structure can be implemented as the equivalent definite data
structure of holders, so there need be no duplication of implementations. That
one cannot use a single pkg for both does result in duplication of the spec, but
that seems like less of an issue to me.

--
Jeff Carter
"Apple juice and Darvon is fantastic together."
Play It Again, Sam
127

Jeffrey R.Carter

unread,
Sep 2, 2022, 6:55:16 AM9/2/22
to
On 2022-09-02 08:11, Emmanuel Briot wrote:
>
> I did point you to the full repository if you prefer an extensive, real-life code sample. This was just an extract showing the gist of the issue.
>
> One can check the full code and extensive documentation on the design in the github repository.

Yes, I looked at it, and found it over complicated, difficult to understand, and
not user friendly. There appears to be extensive HTML documentation, but it can
only be viewed as HTML source in Github, which is not easy to read, so I didn't.

Apparently a user who wants a map has to supply a significant part of what I
consider the implementation of the map. As such, this may serve as a hidden
layer to implement a library of data structures, but seems unusable for the
typical user, for whom using a hashed-map abstraction should be as simple as

function Hash (Key : in Identifier) return Hash_Value;

package Maps is new Lib.Maps
(Identifier => Identifier, Associated_Value => Associated_Value);

I was not willing to spend more than about 15 minutes trying to understand this,
so I may be missing something.

> These packages are mostly implementation details. They are used to build high-level packages similar to the Ada containers

This agrees with what I was saying earlier. But even for such a use, simplicity
and ease of understanding are still the most important aspects of S/W. If
attaining them involves some code duplication they are worth that price.

>> that pkg Keys serves some actual role in the spec of Maps, then someone wanting
>> to use pkg Maps will need to understand that role, which involves understanding
>> Definite_Elements, which involves understanding pkg Traits, which involves
>> understanding Elements.
>
> which involves understanding Ada, which involves understanding the compiler, which involves understanding the kernel sources, ...
> How many times have you checked the whole sources of the GNAT runtime to understand how they implemented the Ada containers ?

One understands a pkg by understanding its spec. I find it hard to believe that
you could actually think that I meant one had to understand the implementation
as well.

Dmitry A. Kazakov

unread,
Sep 2, 2022, 7:04:40 AM9/2/22
to
On 2022-09-02 12:41, Jeffrey R.Carter wrote:

> The only indefinite data structure that is needed seems to be holders.

The language should support and encourage design that does not rely on
memory pools.

In my view one of major advantages of Ada is that indefinite objects can
be handled without resorting to hidden or explicit pointers to pools.

Emmanuel Briot

unread,
Sep 2, 2022, 7:20:40 AM9/2/22
to
> not user friendly. There appears to be extensive HTML documentation, but it can
> only be viewed as HTML source in Github, which is not easy to read, so I didn't.

Those are generated from the .rst files in docs_src/

> typical user, for whom using a hashed-map abstraction should be as simple as
>
> function Hash (Key : in Identifier) return Hash_Value;
> package Maps is new Lib.Maps
> (Identifier => Identifier, Associated_Value => Associated_Value);

Maps are created as

package Maps0 is new GAL.Maps.Def_Def_Unbounded
(Integer,
Element_Type => Integer,
Hash => Test_Support.Hash,
Container_Base_Type => GAL.Controlled_Base);

The only addition here is `Container_Base_Type`, and that's because the library is probable with SPARK, and the latter doesn't support controlled types.
And Ada doesn't support default values for the formal parameters of generics (another annoying limitation !)

> I was not willing to spend more than about 15 minutes trying to understand this,
> so I may be missing something.

Fair enough. The library is really a set of experiments, mostly successful, and I think it might have been used for the implementation of the current
SPARK containers in GNAT, though I am not positive there.
I did look at the pragmARC components, and there you indeed chose to have a large number of similar-looking packages and code duplication. I guess
we'll have just to agree to disagree on the design approach there. But of course, users having choices is what makes an ecosystem interesting.

What I was really going after are graphs and their algorithms. In particular, I want those algorithms to work on any graph data structure provided it has
a number of primitive operations. In fact, the algorithm could also work when the graph is kind of implicit in the code, even if we do not have an actual
Graph object. And those this, you need generics.

A similar approach is what Rust uses all over the place with its traits, or what C++ Boost library uses for its graphs, too.

Jeffrey R.Carter

unread,
Sep 2, 2022, 10:28:46 AM9/2/22
to
On 2022-09-02 13:20, Emmanuel Briot wrote:
>
> Maps are created as
>
> package Maps0 is new GAL.Maps.Def_Def_Unbounded
> (Integer,
> Element_Type => Integer,
> Hash => Test_Support.Hash,
> Container_Base_Type => GAL.Controlled_Base);

Where is that? All I saw in my quick look were Maps, Maps.Generics, and Maps.Impl.

Randy Brukardt

unread,
Sep 2, 2022, 8:01:32 PM9/2/22
to
"amo...@unizar.es" <amo...@unizar.es> wrote in message
news:672e9bc6-1e53-42cb...@googlegroups.com...
On Thursday, September 1, 2022 at 11:33:47 PM UTC+2, Jeffrey R.Carter wrote:
> On 2022-09-01 20:54, Emmanuel Briot wrote:

>> As presented, this seems very over complicated. Elements and
>> Definite_Elements
>> add no value, and this is just a complex way of writing

>Going in a tangent, and I guess you know perfectly well, but this is caused
>by the
>painful duplication of code that Ada pushes you to by not having a native
>way to
>abstract storage of definite vs indefinite types.

This is premature optimization at it's worst.

> So the provider of a generic library very soon faces this conundrum about
> duplicating most interfaces, if not implementations, or resort to
> non-trivial
> generics, or accept an unnecessary penalty for definite types, or push to
> the client the definite storage matter.

There is no penalty in a code sharing implementation like Janus/Ada: the
implementation of definite types is essentially the same as you would write
by hand for an indefinite type. In most cases, all one needs is an
indefinite generic.

(The plan for Janus/Ada was always to use post-compilation optimization to
reduce the overhead of generics, but admittedly, that part never got built.
If I had infinite time...)

Assuming otherwise is certainly premature optimization.

> There's simply no satisfying solution here [that I know of]. The
> duplication of
> every standard container to have both [in]definite variants is a strong
> indictment.

The original expectation for the containers was that there would be many
variants of each container, because the needs for memory management, task
management, and persistence differ between applications: there is no
one-size fits all solution.

But I agree on one point: the "basic" container is unncessary; one should
either use the indefinite or bounded container (depending on your memory
management needs, either fully fixed or fully heap-based) -- stopping in the
middle makes little sense.

Randy.


Randy Brukardt

unread,
Sep 2, 2022, 8:07:31 PM9/2/22
to
"Emmanuel Briot" <briot.e...@gmail.com> wrote in message
news:b678b499-0cb1-4c64...@googlegroups.com...

>These packages are mostly implementation details. They are used to build
>high-level
>packages similar to the Ada containers, except with much better code reuse,
>more
> efficient, and SPARK-provable.

(Wading in where I should probably not tread... :-)

But they violate the #1 principle of the Ada.Containers: ease of use. One
principle that we insisted on was that a single instantiation was the
maximum we would use, because we did not want people moving from arrays to
containers to have to replace one declaration with a half page of magic
incantations. (This is the reason that there is no container interface, for
one consequence, and certainly no signature packages.)

In general, people either undertand and like signature packages, or really
do not understand them and just use them when insisted on. The standard
containers in Ada needed to be usable by the maximum number of users, and
insisting on bells and whistles that many don't understand does not help.

Randy.




Randy Brukardt

unread,
Sep 2, 2022, 8:12:29 PM9/2/22
to
"Dmitry A. Kazakov" <mai...@dmitry-kazakov.de> wrote in message
news:teso01$16gg$1...@gioia.aioe.org...
> On 2022-09-02 12:41, Jeffrey R.Carter wrote:
>
>> The only indefinite data structure that is needed seems to be holders.
>
> The language should support and encourage design that does not rely on
> memory pools.
>
> In my view one of major advantages of Ada is that indefinite objects can
> be handled without resorting to hidden or explicit pointers to pools.

But they're implemented with some sort of hidden allocation. (GNAT uses a
"secondary stack", whatever that is, but that is just a restricted form of
pool). Janus/Ada uses built-in pools with cleanup for all such things to
simplify the interface (the code for allocations and stand-alone objects is
mostly shared, both within the compiler and at runtime).

Randy.


Randy Brukardt

unread,
Sep 2, 2022, 8:16:38 PM9/2/22
to
"Emmanuel Briot" <briot.e...@gmail.com> wrote in message
news:bf352372-632c-45c1...@googlegroups.com...
...
> And Ada doesn't support default values for the formal parameters of
> generics (another annoying limitation !)

Ada *DOES* support default values for formal parameters of generics. At
least the most used kinds of parameters, including subprograms and types
(the latter added in Ada 2022). Whether GNAT (or your other favorite
compiler) supports formal type defaults yet is a different question.

Randy.


Dmitry A. Kazakov

unread,
Sep 3, 2022, 4:23:05 AM9/3/22
to
For a programmer that does not matter. The problem with pools is
locking, non-determinism, issues with protected actions. If secondary or
primary stack is the program stack, nobody really cares.

BTW, merely doing pool tracing/bookkeeping becomes a sheer nightmare if
you cannot return a string from a function.

Jeffrey R.Carter

unread,
Sep 3, 2022, 4:59:19 AM9/3/22
to
On 2022-09-03 02:07, Randy Brukardt wrote:
>
> One
> principle that we insisted on was that a single instantiation was the
> maximum we would use

Except for queues

--
Jeff Carter
"In our experiments, the [Ada] compile[r] was able to find not
just typographical errors, but also conceptual errors in the
development of the code."
Scott and Bagheri
161

Simon Wright

unread,
Sep 3, 2022, 3:00:04 PM9/3/22
to
"Randy Brukardt" <ra...@rrsoftware.com> writes:

> But they violate the #1 principle of the Ada.Containers: ease of
> use. One principle that we insisted on was that a single instantiation
> was the maximum we would use

And this was one reason that I didn't put up any arguments at Ada Europe
2002 for the Ada 95 Booch Components to form a basis for Ada.Containers
- you'd need 3 instantiations, one after the other.

-- A company's Fleet holds a number of Cars.

with BC.Containers.Collections.Bounded;
with Cars;
package My_Fleet is

use type Cars.Car;

package Abstract_Car_Containers
is new BC.Containers (Cars.Car);

package Abstract_Car_Collections
is new Abstract_Car_Containers.Collections;

package Fleets
is new Abstract_Car_Collections.Bounded (Maximum_Size => 30);

The_Fleet : Fleets.Collection;

end My_Fleet;

The other was a lack of consistency in the implementation (Length?
Size?).

Emmanuel Briot

unread,
Sep 5, 2022, 2:56:45 AM9/5/22
to
Jeff Carter:
> Where is that? All I saw in my quick look were Maps, Maps.Generics, and Maps.Impl.

Simon Wright:
> And this was one reason that I didn't put up any arguments at Ada Europe
> 2002 for the Ada 95 Booch Components to form a basis for Ada.Containers
> - you'd need 3 instantiations, one after the other.

I definitely see the same issue. The way my library is trying to workaround that is as follows:
Those instantiations are only needed for people who want/need to control every aspects of their containers, for instance
how elements are stored, how/when memory is allocated, what is the growth strategy for vectors, and so on.
Most users should not have to care about that in practice. So we use code generation at compile time to generate
high-level packages similar to the Ada containers, with a limited set of formal parameters (in src/generated, to be
more specific). We can generate bounded/unbounded versions, definite/indefinite versions, and any combination of
those.
One of the intentions of the library, initially, had been the implementation of the Ada containers and SPARK containers
in GNAT, as a way to share as much code as possible between the two.


Randy Brukardt:
> Assuming otherwise is certainly premature optimization.

I am quoting a bit out of context, though I believe it is close enough. Designers of containers must care about performance
from the get-go. Otherwise, people might just as well use a list for everything and just traverse the list all the time. We all
know this would be way too inefficient, of course, which is why there are various sorts of containers.
Anyone who has actually written performance-sensitive code knows that memory allocations is definitely something to watch
out for, and the library design should definitely take that into account.

Jeff Carter:
> The only indefinite data structure that is needed seems to be holders

Although it is certainly true that using holders works, it is not applicable when designing a containers library that intends to be
mostly compatible with Ada containers. The latter have chosen, long ago and before Holder was a thing, to have definite and
indefinite versions. The main benefit to this approach is that users still retrieve directly the type they are interested in (e.g. String)
rather than a holder-to-string.
I must admit I have very limited experience with Holders, which I have never used in production code (nor, apparently, have my
colleagues and ex-colleagues).


Randy Brukardt:
> Ada *DOES* support default values for formal parameters of generics

Hey, I just discovered that, thanks Randy ! For people who also did not know that:

generic
type Item_Count is range <> or use Natural;
package Gen is

It is supported by GNAT's newer versions (I don't know when it was implemented though)

Dmitry A. Kazakov

unread,
Sep 5, 2022, 3:34:42 AM9/5/22
to
On 2022-09-05 08:56, Emmanuel Briot wrote:

> Although it is certainly true that using holders works, it is not applicable when designing a containers library that intends to be
> mostly compatible with Ada containers.

Right. Holder requires finalization and finalization means language
prescribed finalization lists which is highly undesirable in many cases.

> The main benefit to this approach is that users still retrieve directly the type they are interested in (e.g. String)
> rather than a holder-to-string.

And that the container designer has control over the pool where the
items get actually allocated.

> I must admit I have very limited experience with Holders, which I have never used in production code (nor, apparently, have my
> colleagues and ex-colleagues).

I have been using the idea for a long time, since Ada 95 before the
standard library had them. In my experience holders multiply the number
of container variants:

1. Definite elements
2. Indefinite elements
+
3. Holder elements in the interface (and maybe implementation)

The third gets a holder package as a formal parameter or, alternatively,
is a child of a holder package (for performance reasons). The container
interface has direct operations in terms of the Element_Type as well as
in terms of the holder type.

Sometimes the holder variant is actually the indefinite one that
promotes holders only in its interface.

P.S. In my opinion helper types/package is an evil of far greater scale
than any premature optimization!

The programmers doing the latter at least try to understand the code
they write.

amo...@unizar.es

unread,
Sep 5, 2022, 4:53:21 AM9/5/22
to
On Saturday, September 3, 2022 at 2:01:32 AM UTC+2, Randy Brukardt wrote:
> "amo...@unizar.es" <amo...@unizar.es> wrote in message
> news:672e9bc6-1e53-42cb...@googlegroups.com...
> On Thursday, September 1, 2022 at 11:33:47 PM UTC+2, Jeffrey R.Carter wrote:
> > On 2022-09-01 20:54, Emmanuel Briot wrote:
>
> >> As presented, this seems very over complicated. Elements and
> >> Definite_Elements
> >> add no value, and this is just a complex way of writing
>
> >Going in a tangent, and I guess you know perfectly well, but this is caused
> >by the
> >painful duplication of code that Ada pushes you to by not having a native
> >way to
> >abstract storage of definite vs indefinite types.

> This is premature optimization at it's worst.

Just because the language doesn't offer a way to do it. Otherwise I wouldn't need to care.

> > So the provider of a generic library very soon faces this conundrum about
> > duplicating most interfaces, if not implementations, or resort to
> > non-trivial
> > generics, or accept an unnecessary penalty for definite types, or push to
> > the client the definite storage matter.
> There is no penalty in a code sharing implementation like Janus/Ada: the
> implementation of definite types is essentially the same as you would write
> by hand for an indefinite type. In most cases, all one needs is an
> indefinite generic.

Well, that sounds neat for Janus/Ada, but is a different issue to clients having to wrap their indefinite types prior to instantiation, and suffer the unwrapping throughout the code.

> (The plan for Janus/Ada was always to use post-compilation optimization to
> reduce the overhead of generics, but admittedly, that part never got built.
> If I had infinite time...)
>
> Assuming otherwise is certainly premature optimization.

I'm of the opinion that it goes beyond just premature optimization, in the terrain of readability/maintainability by causing boilerplate, and when generics specializations do become necessary, by causing code duplication.

> > There's simply no satisfying solution here [that I know of]. The
> > duplication of
> > every standard container to have both [in]definite variants is a strong
> > indictment.
> The original expectation for the containers was that there would be many
> variants of each container, because the needs for memory management, task
> management, and persistence differ between applications: there is no
> one-size fits all solution.
>
> But I agree on one point: the "basic" container is unncessary; one should
> either use the indefinite or bounded container (depending on your memory
> management needs, either fully fixed or fully heap-based) -- stopping in the
> middle makes little sense.

That makes sense, right.

>
> Randy.

amo...@unizar.es

unread,
Sep 5, 2022, 4:56:42 AM9/5/22
to
On Monday, September 5, 2022 at 10:53:21 AM UTC+2, amo...@unizar.es wrote:
> On Saturday, September 3, 2022 at 2:01:32 AM UTC+2, Randy Brukardt wrote:

> Well, that sounds neat for Janus/Ada, but is a different issue to clients having to wrap their indefinite types prior to instantiation, and suffer the unwrapping throughout the code.

Drats, I was here obviously thinking about the contrary case where only the definite container exists...

Jeffrey R.Carter

unread,
Sep 5, 2022, 5:30:59 AM9/5/22
to
On 2022-09-05 08:56, Emmanuel Briot wrote:
>
> I definitely see the same issue. The way my library is trying to workaround that is as follows:
> Those instantiations are only needed for people who want/need to control every aspects of their containers, for instance
> how elements are stored, how/when memory is allocated, what is the growth strategy for vectors, and so on.
> Most users should not have to care about that in practice. So we use code generation at compile time to generate
> high-level packages similar to the Ada containers, with a limited set of formal parameters (in src/generated, to be
> more specific). We can generate bounded/unbounded versions, definite/indefinite versions, and any combination of
> those.

This seems backwards. The user should encounter the forms most likely to be used
first; if part of the packages are in a subdirectory, those should be the ones
less likely for the typical user to use.

> Although it is certainly true that using holders works, it is not applicable when designing a containers library that intends to be
> mostly compatible with Ada containers. The latter have chosen, long ago and before Holder was a thing, to have definite and
> indefinite versions. The main benefit to this approach is that users still retrieve directly the type they are interested in (e.g. String)
> rather than a holder-to-string.

Before Ada.Containers existed, I commonly used definite data structures from the
PragmARCs with Unbounded_String, which is partly a specialized holder for String
(and partly a specialized Vector for Positive/Character). Generalizing from this
led to implementing a Holder pkg.

When I said a holder is the only indefinite pkg that is needed, I meant to imply
that other indefinite structures would be implemented using the definite form +
holder.

--
Jeff Carter
“Bug rates in C++ are running higher even than C ...”
Stephen F. Zeigler
216

Randy Brukardt

unread,
Sep 6, 2022, 8:43:01 PM9/6/22
to
"Jeffrey R.Carter" <spam.jrc...@spam.acm.org.not> wrote in message
news:tev515$2rbj5$1...@dont-email.me...
> On 2022-09-03 02:07, Randy Brukardt wrote:
>>
>> One
>> principle that we insisted on was that a single instantiation was the
>> maximum we would use
>
> Except for queues

Right, and one consequence of that is that the queues aren't used much. (Not
sure if they would be used much in any case, they're definitely a
specialized need compared to a map.)

Randy.


Randy Brukardt

unread,
Sep 6, 2022, 8:51:48 PM9/6/22
to
"Emmanuel Briot" <briot.e...@gmail.com> wrote in message
news:458444ed-e384-4663...@googlegroups.com...
...
> Randy Brukardt:
>> Assuming otherwise is certainly premature optimization.
>
> I am quoting a bit out of context, though I believe it is close enough.
> Designers of containers must care about performance
> from the get-go. Otherwise, people might just as well use a list for
> everything and just traverse the list all the time. We all
> know this would be way too inefficient, of course, which is why there
> are various sorts of containers. Anyone who has actually written
> performance-sensitive code knows that memory allocations is definitely
> something to watch out for, and the library design should definitely take
> that into account.

Definitely out of context. If you have code which is truly performance
sensitive, then it cannot also be portable Ada code. That's because of the
wide variety of implementation techniques, especially for generics. (For
Janus/Ada, if you have critical performance needs, you have to avoid the use
of generics in those critical paths -- sharing overhead is non-zero.)

I agree that the design of the containers matters (which is why the we made
the sets of operations for the various containers as close as possible, so
switching containers is relatively easy). But the indefinite/definite thing
is premature optimization - it makes little difference for a Janus/Ada
generic, at least in the absence of the full-program optimizer (that we
never built). If your code isn't intended to be portable Ada code, then
*maybe* it makes sense to worry about such things. But the expectation was
always that containers would be useful in cases where the performance is
*not* critical - one probably should use a custom data structure for
performance critical things. (But most things aren't really performance
critical in reality.)

Randy.


0 new messages