Am I doing something wrong here?

I use Ada for my personal projects, largely out of admiration for the language, but I’m entirely self-taught in Ada, and the literature on its usage is… well, somewhat scant in comparison to, say, C++.

Not every language is perfect, but some things in the standard Ada library seem far too cumbersome. So much so, that I have to wonder if I’m doing things right. I’ve looked at the ARM, perhaps not well, and rosetta code is down at the moment, so I thought I’d inquire here.

Queues

Setting up a queue seems to involve way more effort than it should:

  • You have to include two separate packages:
    • Containers.Synchronized_Queue_Interfaces
    • Containers.Unbounded_Synchronized_Queues or one of the other three variants
  • You have to use all type Ada.Containers.Count_Type if you want to check whether it’s empty.

The payoff? For all the effort you put into creating it, the resulting queue offers less functionality than other languages’. All you can do is Enqueue, Dequeue, and check usage. You can’t iterate through the queue to see what’s there and possibly modify it, nor search through it, let alone split the queue. There isn’t even an .Is_Empty method to tell you when it’s exhausted.

Queues are pretty common, and I’ve never seen one so bare bones. Is there something more functional than this in the standard library? If not, do people recommend a different library (e.g,. the Simple Components library) or do you typically roll your own?

(I realize there may be good reasons for the library to do things this way; I’m just wondering what people do about it.)

Iteration through maps

Suppose I set up a map M and want to iterate through it.

  • for E of M iterates through the map’s elements, not through its keys. I wish they had returned the keys instead, since often that’s what I want, and it’s easy to obtain the elements from the keys, and not so much the converse. But this does make sense.
  • for C in M.Iterate iterates a cursor through the key/pair combinations. This makes sense, too. But…
  • Using Cursor is, again, cumbersome. As far as I can tell, I can’t do this: C.Key or C.Element. Instead, I have to with all type Map_Type.Cursor to get Key(C) and Element(C), or else qualify the functions fully: Map_Type.Key(C) or Map_Type.Element(C).

(I think Ada 2022 introduces functionality to let me treat a cursor the way I want, but if so, I have to update my gnat: version 20210519-103.)

So what am I doing wrong?

Have I missed something, or is this indeed the standard way of doing these things in Ada? I would be grateful for any direction, because I don’t use Ada often enough to remember these two things, and the compiler’s help error messages are not the greatest.

Honestly!

I apologize if it comes across as a complaint. I realize there’s no perfect language, and just want to know if I’m missing something, because Ada does so many things right. I can make much, much longer lists of my complaints with C++, and a somewhat longer list of complaints with Rust, than I can with Ada.

1 Like

I’m pretty ignorant about what’s available in Ada, but I was wondering whether the Doubly_Linked_Lists package would give you the kind of queue you wanted since it is easy to append to the end and pull things off the front, you can iterate through it and do all sorts of things.

I am curious to see what other folks have to say!

2 Likes

I’ll echo @wutka here, it sounds like you should be using Doubly_Linked_Lists or Vectors instead of a Queue. I believe those synchronized queue packages are intended for safely passing messages between tasks.

I agree that iteration using cursors is a bit cumbersome. AdaCore’s blog post has an example of the non-standard 'Map and 'Reduce attributes that may be easier to use, depending on what you’re trying to do.

[RFC] use of 'dot' notation on untagged types by sttaft · Pull Request #34 · AdaCore/ada-spark-rfcs · GitHub is the proposal to support the C.Element syntax you suggested. I haven’t seen much argument against this, so I assume it will be supported by GNAT soon.

I thought of using those! but I had forgotten how to use them, and in fact I think this is another case where I really need to upgrade my compiler first.

Thanks for that link to the blog post all the same: not only does it let me refresh my familiarity with those attributes, I see Put_Image, which I had forgotten about, and have sorely wanted… though again the text implies it’s probably changed since my compiler was released.

Iterator filters is another thing I’d wanted recently (I’ve made use of it in Kotlin and Rust, I think) and there they are. Nice!

Just discovered that the dot syntax for untagged types is already implemented with the -gnatX flag.

Doubly linked lists sounds interesting, but is this a bug in my version of gnat?

linked_list.append(element);

element is a record with non-default values (verified by inspection), but the element inserted at the end of the list has default values!

You’re right about the Q containers: they’re poorly designed. But the original version provided no way to create a Q object, so what we have is at least an improvement over that. Another problem is that only synchronized and priority queues are provided, and people often want other kinds.

A design decision for the containers was that things that are “easy to implement” should not be included, though there was no definition of how that should be determined. Qs were considered easy to implement, so they did not exist in the original version of the containers. But synchronized and priority Qs were then determined to not be easy to implement, so the existing Qs were added.

The PragmAda Reusable Components include a number of Qs, named PragmARC.Data_Structures.Queues.*. Those who make frequent use of Qs may find them useful.

I usually iterate over the containers using the Iterate procedure.

Note that you never have to say use all type; in fact, I never have. But use type can be a good idea sometimes.

Thank you for the feedback! The context is helpful. I visited the link, but I didn’t get a chance to look at it carefully. I’ll try to look at it more carefully soon, since I’ve been using queues a lot lately in Advent of Code.

I was under the impression that I cannot even do something as simple as while Queue.Current_Use > 0 unless I use all type, but apparently I misremembered. (I checked!) Now I just have to look up the difference is between use type and use all type

Thank you again!

use type makes the primitive operators of the type directly visible; use all type does the same for the primitive operations. The primitive operators are a subset of the primitive operations. But there are a number of ways to call an operator such as ">" defined for a type T in a package P:

  • P.">" (L, R)
  • function ">" (Left : in P.T; Right : in P.T) return Boolean renames P.">";
  • use type P.T;
  • use all type P.T;
  • use P;

If T is tagged, there’s also L.">" (R).

1 Like