How do you get around the absence of containers for limited types?

Everything is in the question.
How do you do ? I just wanted an array for my limited type, but an extensible one. Seems like in 2022 something that simple is still not there ?
The point was also to not have to bother about memory management, so access types would defeat the purpose.
I suppose I could write my own container with the Aggregate attribute, but ughhh

If a variable is of a limited type, it cannot be copied. If you cannot reference it with an access type either, then how do you expect to insert it into any data structure?

My general means for insertion when I roll my own is to have operations like this:

procedure Insert
   (Self        : in out Limited_Map; 
    Key         : Non_Limited_Key_Type;
    Constructor : not null access function return Limited_Type);

if a type doesn’t already have a constructor of the type it is really trivial to make one:

function Make_Limited_Type return Limited_Type is
begin
   return Result : Limited_Type; -- can use extended return to do init if needed
end Make_Limited_Type;

Since these can be nested operations, you can actually make some pretty versatile insertion operations this way.

I’ve found searchable maps to be one area where I most often need containers for limited types. I’ve had code with really large collections of limited types that I added maps for so i can easily search through them. I’ve also used Stack implementations for limited types to make some algorithms much easier to code.

Unfortunately that generally is what I had to do. I never found much support when asking for such things as containers for limited objects in the past. I’ve had various use cases for them though.

One of these days I may get around to making some public ones but the problem with container design in Ada is the lack of actual language tracked references. The implicit dereference types typically used need a controlled type to enforce memory safety and that is a pretty big hamper to performance usually. So it makes solutions in this arena clunky for general use.

I meant to create variables in situ, just like with an array of a limited type. Just extensible. No copy, no moving data around. Okay, I’ll do like Jere says, I had reached the same conclusion. This is a language’s limitation. Things we can do with arrays (including slices by the way…) should be doable with containers. The semantics should suffer from implementation hurdles.
But as for the “language tracked references”, I’m not sure what you mean ? Are you refering to Rust’ borrowed types or something ? If so please provide a concrete exemple, because I know nothing about these things.

In order to do custom iteration for example requires the use of some type of reference. Currently the Ada standard has us use types with implicit_dereference set but out of the box those can dangle which is not safe. We need a mechanism in the language. A tracked reference is one where the compiler keeps track of it preventing it from dangling. It doesn’t have to be as strict as what rust does but something like that also works.

Doesn’t spark have this now? It also goes further than Rust in preventing memory leaks.

it might. I don’t use spark, so less familiar with it.

How do you get around the absence of containers for limited types?

Hm.
I think what I’ve done is typically something like:
Type Handle is not null access [constant] Limited_Type
and use containers on Handle.

Another option is something like:

  -- If we need to ensure clean-up, derive from Controlled.
Type Example(Link : not null access Limited_Type) is null record;

That’s an issue I had been trying to solve when I experiment with the traits containers a few years ago (GitHub - briot/ada-traits-containers: Generic Ada Library for Algorithms and Containers and ada-traits-containers/doc/making_of.rst at 4cebb62525296f74f0612acb06ed5c2d08588a7b · AdaCore/ada-traits-containers · GitHub).

Basically, and as stated by others earlier in this discussion, the idea is to use an access type. But the standard Ada containers will not let you have a callback or hook when an element is removed from the container (for instance because it is replaced by another value). So you end up wrapping a lot of the operations to provide the memory management on top.

Another approach would be to use a Ada.Containers.Indefinite_Holders.Holder , but this comes with performance cost.

So the traits containers would provide a generic low-level container (gal-lists-generics.ads in my repository) and a number of higher-level wrappers (GAL.Lists.Definite_Unbounded_Limited for instance).

These containers were discussed here a few days ago, but were never completed, so they likely will not fit your needs as is. But perhaps they can server as inspiration.

1 Like

I’ll bookmark this post, when I’m finished with this book and another on spark and contributing to this seems appealing, and certainly more immediately accessible than on OSes. I especially like attempts to leverage generics to the extreme. If everyone did it I’m sure those AIs addressing the missing defaults and other facilities, would have passed years ago. We may easily ignore deficiencies if we meet them only once in a while in our code, but if they become our daily bread, then it is another story.

Here is kind of a first stab at a limited holder type (which might be a stepping stone to containers). It’s still a bit in its infancy (I’ve had most of it lying around on my local machine for a few years, but decided to finish it out last night, and did some preliminary unit testing on it.).

The main idea for it is the reference types cannot dangle unless you turn on unchecked mode (for performance). By default they detect dangling and also prevent use after free at the same time.

holders: bullfrog/src/bullfrog-containers-limited_indefinite_holders.ads at 0420184ead40fe6ce418d14d738887d9fc779807 · jere-software/bullfrog · GitHub

It would help to have an idea what problem you’re trying to solve to give you useful replies.

Me ? well I needed to use a map container, and realized my type was limited so it wouldn’t work. That’s about it.

This is how you tried to solve the problem, not the problem.

No, the problem.
All of the the (1) “executive summary”, (2) “overview”, and (3) “here’s where I’m stuck”.

  1. RFC-822 email addresses.
  2. E-mail addresses can have comments.
  3. I’m trying to use a linked list to compose the parts, but am finding it difficult to handle comments.

Ok, Here I need a way to assemble the objects representing cells in a spreadsheet.
The book used pointers everywhere, but I don’t like that, I prefer to stick closer the semantics and handle class-wide type in subprograms’ profiles directly.
The issue becomes, that the list package used by this program only copes with private types, and class wide object are indefinite… and here, limited as well.
I am thinking of changing the list to contain indefinite types.

I think there are only two ways to make it work.
I don’t want to pass on access values, so since copying objects in the list won’t be allowed, I’ll need to :

  1. etiher manipulate references, taken from the objects passed in parameters, but then the objects have to be created outside the generic list package, and most likely I’ll need Unchecked_Access. Am I right ?
  2. Or, I have to pass on a constructor to the generic, to build the objects in situ, directly in the allocator. Since the cells won’t exist outside the list, I prefer this.

Hm, maybe something like:

   Type Data is Whatever'Class;

   Subtype Alpha   is Character range 'A'..'Z';
   Subtype Numeric is Positive  range 1..255;

   Type Coordinate is record
      Column : Alpha;
      Row    : Numeric;
   end record;

   Package Spreadsheet_Map is
     new Ada.Containers.Indefinite_Ordered_Maps
      (  Key => Coordinate, Element => Data  );

And so on.
Actually, maybe this is better:

   Type Coordinate (Col : Alpha; Row : Numeric) is null record;
1 Like

Hi, I’m about to finish the list ADT, following my original idea. It appears that passing on a constructor was more elegant that anticipated, because the generic doesn’t have to take any parameter hence doesn’t have to know anything about the quantity of types of these parameters, if the function I give is nested and so takes the parameters from the context.
That way if I derive spreadsheet_type with derives cell_types I can use make lists with cells of completely different parameters, say a picture.
Since I would have to rewrite Insert anyway (to check the syntax), this is perfect.

Which means:

generic
   type Item_Type (<>) is limited private;
   type Item_Access is access Item_Type;
package JE.Limited_Lists is ...
	generic
		with function Make return Item_type is <>;
	procedure Insert (Iterator : List_Iterator);
end JE.Limited_Lists;
package Cell_Lists is new JE.Limited_Lists (Cell_type'Class, Cell_Access);

package JE.Spreadsheets is ... 
	procedure Insert (Sheet : in out Spreadsheet_Type;
                  Where, Value : String) is
    	function Make return Cell_type'Class is -- use Where and Value
    	function Insert is new Cell_Lists.Insert; -- L89
	begin
		...
	end Insert;
end JE.Spreadsheet;

But now I have

je-spreadsheets.adb:89:42: error: “Insert” is not the name of a generic function

But as you see Insert IS under Cell_Lists… What gives ? It’s not a name conflict,

I’ll be really happy when it’s done, I’ll have worked a ton of notions at once: class-wide programming/dynamic dispatching, generics extended to the limit, and all the hassle dealing with limited types brings.