What is Ada.Containers.Synchronized_Queue_Interfaces good for?

I have been looking at Ada.Containers.Synchronized_Queue_Interfaces and how it is used in Ada.Containers.Bounded_Synchronized_Queues and I wonder why they are two different packages.

There is no way to extend the interface or provide a different implementation. All the advantages of using an interface seem to be unavailable because are linked inseparably. There is no way to provide a different implementation.

What is the point. Just reusing 15 lines of code so you don’t need to repeat them in the 5 queue packages?

Background: I’m woking on a Queue with an “finished” / “end of queue” status and it seems the only way to do is to implement it from scratch.

Protected types and task types are non-tagged. It was a design flaw of Ada 95 when tagged types were introduced first. It is still here, because it seems more important to introduce square brackets and executable code in declaration… :disappointed:

I do not know what you are trying to do, but making a queue synchronized is a bad idea anyway. For a protected object protected action might be too heavyweight. For a task the overhead is too big.

Depending on the queue type it is better to have it tagged and use locking or no locking according to the use case. For example, 1-1 FIFO needs no locking at all. Otherwise, it is usually some sort of mutex/event combination you could attach to the queue to perform interlocking/signalling,

1 Like

Synchronized_Queue_Interfaces is a thread-safe interface for a queue that operates on a generic type, that you feed into a another package type to make an unbounded or bounded queue. I can’t elaborate personally more on the design, other than there’s an explanationale in the Ada 2012 Rationale.

Regarding “How useful is this?” I use it in my septum project to track remaining files to load and analyze as they’re found by the directory walker. It’s a piece of code that in Ada seems to work perfectly with almost no effort compared to implementing the same thing in the other languages to which I’ve ported the project.

4 Likes

Can you expound on this a bit more. I was able to extend the interface separately with my own custom type (I’ll give a stub example below). I know extending interfaces with protected types is not intuitive due to how they handle the self/this variable and also because of interchangeability of entry/procedure.

I’m hoping maybe it’s just a typo somewhere for you.

In the general case, having a synchronized queue opens you up the world of task and synchronized features of Ada. Like you can have a queue and try to put an item on it but also have a timeout in case it stays full too long.

    v : Custom_Queues.My_Queue;
begin
    select
        v.Enqueue(10);
        Put_Line("Enqueued");
    or delay 3.0;
        Put_Line("Timed out");
    end select;

One reason to have the two packages separated is so that you can define multiple different protected object implementations with different default priorities and still use them all with the same interface. Don’t know how useful that is in practice

That said, I agree with you that it seems really odd to split them. I’d have to go back and look at the ARG meeting minutes or AI discussion on it to see the exact reasoning.

Stub example:

with Ada.Text_IO; 
    use Ada.Text_IO;
with Ada.Containers.Synchronized_Queue_Interfaces; 
    use Ada.Containers;

procedure jdoodle is

    package My_Queue_Interfaces is new Synchronized_Queue_Interfaces(Integer);
    
    package Custom_Queues is
    
        type Data_Array is array(Positive range <>) of Integer;
    
        protected type My_Queue is new My_Queue_Interfaces.Queue with
            overriding entry Enqueue(New_Item  : in     Integer);
            overriding entry Dequeue(Element   :    out Integer);
            overriding function Current_Use return Count_Type;
            overriding function Peak_Use return Count_Type;
        private
            Dummy : Integer := 0;
        end My_Queue;
    
    end Custom_Queues;
    
    package body Custom_Queues is
    
        protected body My_Queue is
        
            entry Enqueue(New_Item : in Integer) when True is
            begin
                null;
            end Enqueue;
            
            entry Dequeue(Element : out Integer) when True is
            begin
                null;
            end Dequeue;
            
            function Current_Use return Count_Type is (0);
            
            function Peak_Use return Count_Type is (0);
            
        end My_Queue;
        
    end Custom_Queues;
    
begin
    null;
end jdoodle;
1 Like

Isn’t Synchronized_Queue_Interface also necessary for Ada.Containers.Unbounded_Priority_Queues? That would see a reason to separate it from Bounded_Synchronized_Queues, or am I off base? (I often am! :grin:)

2 Likes

Yep yep. I think the OP was wondering why they chose to break out the interface separately rather than just making the different queues without interfaces involved at all. I was guessing it had to do with being able to develop code that could use any of those different types of queues mixed together (including your own custom implementations). But that is only a guess on my end.

2 Likes

I don’t know if any answers are in here, but here is the discussion on it when they were developing it for the language: http://www.ada-auth.org/cgi-bin/cvsweb.cgi/ai05s/ai05-0159-1.txt?rev=1.20

The discussion starts pretty far down. You can search the page for the first instance of
****************************************************************
to find where the discussion starts. It was all done over email there.

2 Likes

Synchronized_Queue_Interfaces is good for two things:

  1. Making the standard queue packages more complex, harder to understand, and more difficult to use
  2. Serving as a cautionary example against the use of programming by extension
2 Likes

I know what queues are good for :wink: It is this part which baffles me:

generic
   with package Queue_Interfaces is
      new Ada.Containers.Synchronized_Queue_Interfaces (<>);

By using the package and not the interface as formal parameter you can’t actually extend from the interface. But then synchronised interfaces are very limited in use anyway

Thanks for the sample. I already expected that this is what I need to do. And you can’t even use the delegate pattern because this:

   protected body Protected_Data is
      procedure Enqueue (Value : in Contained_Type) is
         pragma Debug (AdaCL.Trace.Entering (In_Parameter => Value'Image));
      begin
         Delegate.Get.Enqueue (Value);
      end Enqueue;

results in this:

/Users/Shared/Work/Projects/AdaCL/adacl/src/adacl-queue-protect.adb:55:22: warning: potentially blocking operation in protected operation [enabled by default]

Yes, it’s needed. However copying the 15 lines of code 6 times would have had the same net result but would have been easier to use and more in line with the rest Ada.Containers. Singe package use was the main advantage Ada.Container has over the Booch components which otherwise has more flexible and capable.

Only — you can’t extend anything.

1 Like

Or override anything…

2 Likes

You should still be able to extend from it. The interface is part of the package being withed in, so you would just extend Queue_Interfaces.Queue. Do you mean extend in a non traditional / standard way?

Yeah, if you are going to use delegation, then you want the delegating code to not be protected itself, since the operations it calls are already protected and could be blocking if entries. In Ada that mean you are using operations that take Queue'Class as a parameter or objects of type Queue'Class. Here are some examples (not the only ways, jus some of the ways):

package Queue_Delegates is
        package Queue_Interfaces is new Ada.Containers.Synchronized_Queue_Interfaces(Integer);
        
        subtype Queue is Queue_Interfaces.Queue; -- local renaming for easier use later
        
        -- NOTE:  Two different ways to do this
        
        -- Method 1:  Object based method (bottom up design)
        type Queue_Controller
            -- For simplicity I'm using a constant parameter here.  You can do a more variable
            -- approach but that involves either rolling your own manual memory management or 
            -- using someone elses (like my Limited_Indefinite_Holders package for example);
            (Queue : not null access Queue_Interfaces.Queue'Class) -- not the 'Class here
        is tagged limited null record;
        
        procedure Enqueue(Self : in out Queue_Controller; Value : Integer);
        procedure Dequeue(Self : in out Queue_Controller; Value : out Integer);
        function Current_Use(Self : Queue_Controller) return Ada.Containers.Count_Type;
        function Peak_Use(Self : Queue_Controller) return Ada.Containers.Count_type;
        
        -- Method 2: Procedure based method (top down design)
        procedure Enqueue(Queue : in out Queue_Interfaces.Queue'Class; Value : Integer);
        procedure Dequeue(Queue : in out Queue_Interfaces.Queue'Class; Value : out Integer);
        function Current_Use(Queue : Queue_Interfaces.Queue'Class) return Ada.Containers.Count_Type;
        function Peak_Use(Queue : Queue_Interfaces.Queue'Class) return Ada.Containers.Count_type;
        
    end Queue_Delegates;
    
    package body Queue_Delegates is
    
        procedure Enqueue(Self : in out Queue_Controller; Value : Integer) is
        begin
            Self.Queue.Enqueue(Value);
        end Enqueue;
        
        procedure Dequeue(Self : in out Queue_Controller; Value : out Integer) is
        begin
            Self.Queue.Dequeue(Value);
        end Dequeue;
        
        function Current_Use(Self : Queue_Controller) return Ada.Containers.Count_Type is
            (Self.Queue.Current_Use);
        
        function Peak_Use(Self : Queue_Controller) return Ada.Containers.Count_type is
            (Self.Queue.Peak_Use);
            
            
        procedure Enqueue(Queue : in out Queue_Interfaces.Queue'Class; Value : Integer) is
        begin
            Queue.Enqueue(Value);
        end Enqueue;
        
        procedure Dequeue(Queue : in out Queue_Interfaces.Queue'Class; Value : out Integer) is
        begin
            Queue.Dequeue(Value);
        end Dequeue;
        
        function Current_Use(Queue : Queue_Interfaces.Queue'Class) return Ada.Containers.Count_Type is
            (Queue.Current_Use);
        
        function Peak_Use(Queue : Queue_Interfaces.Queue'Class) return Ada.Containers.Count_type is
            (Queue.Peak_Use);
        
    end Queue_Delegates;

Then you can define multiple different queue types based on the universal interface defined above:

package Predefined_Queues is
        use Queue_Delegates;
        
        -- Multiple different queue types with the same interface
        package Unbounded is new Ada.Containers.Unbounded_Synchronized_Queues(Queue_Interfaces);
        package Bounded_100 is new Ada.Containers.Bounded_Synchronized_Queues(Queue_Interfaces, 100);
        package Bounded_525 is new Ada.Containers.Bounded_Synchronized_Queues(Queue_Interfaces, 525);
        
        subtype Unbounded_Queue_1 is Unbounded.Queue;
        subtype Bounded_Queue_1 is Bounded_100.Queue;
        subtype Bounded_Queue_2 is Bounded_525.Queue;
            
    end Predefined_Queues;

Then you can use them in various ways, all through the same interface (either using a controller or just classwide operations):

   -- Can be any of the types above or even a custom type by your
    Queue_1 : aliased Predefined_Queues.Unbounded_Queue_1; 
    
    Controller_1 : Queue_Delegates.Queue_Controller(Queue_1'Access);
    
    Value : Integer := 0;
    
    Queue_2 : aliased Predefined_Queues.Bounded_Queue_1;
    Queue_3 : aliased Predefined_Queues.Bounded_Queue_2;

begin
    -- Use enqueue for each of the objects
    Queue_1.Enqueue(123);
    Queue_2.Enqueue(1);
    Queue_2.Enqueue(20);
    
    -- Use enqueue from a controller object
    Controller_1.Enqueue(456);
    
    -- Use enqueue from a classwide operation
    Queue_Delegates.Enqueue(Queue_2, 1000);
    
    -- Lets see some results
    Controller_1.Dequeue(Value);
    Put_Line(Value'Image);
    
    
    Queue_Delegates.Dequeue(Queue_1, Value);
    Put_Line(Value'Image);

NOTE: I simplified a lot of things (like assuming integer queues) to keep this as compact as I could.

1 Like

Yes, you can extend the interface. But Ada.Container can’t use the new interface. You then have to write a new container class.

What I’m currently doing, using the Booch components is.

      entry Dequeue (
         Value            : out Contained_Type;
         EmptyAndFinished : out Boolean) when
            not Element_List.Is_Empty (Data) or else
            Finish_Flag is

The Finish_Flag allows me to signal that the last element has been queued and the queue should wind down when empty instead of waiting for another element.

Sadly Ada.Container doesn’t handle halting.

Yes, this is a limitation of the Ada standard in my mind. The standard library has poor support for limited types in the context of containers. You either have to roll your own or use someone else’s. I made a Limited_Indefinite_Holders package for myself because of this. I’ll one day make lists/maps, but I rarely have free time enough these days.

1 Like

But the problem is that with the Finish_Flag I have 2nd condition for which to wait. I’m considering options now.

I have some of those as well modelled on C++ std::unique_ptr and std::shared_ptr. I guess everybody eventually needs some of those. Unlike Ada.Containers.Indefinite_Holders mine holds access types which also allows for limited types.

In my example, if since the non-protected code is just a wrapper for protected code, you could still use the Finish_Flag. You can even add getters or setters for it if you need by adding to the interface.

type My_Queue_Interface is synchronized interface and Queue_Interfaces.Queue;
procedure Set_Finished(Queue : in out My_Queue_Interface; Finished : Boolean) is abstract;

Note that I don’t know your full use case, so this is just for example.

Also note this assumes you are making your own custom queue based off of Ada.Containers.Synchronized_Queue_Interfaces

1 Like