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…
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,
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.
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;
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! )
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.
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.
I know what queues are good for 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.
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.
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.
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.
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