What is the need for an Immutable Queue?
Asked Answered
E

1

8

I have been working with Java for several years now. Recently came across Vavr, a functional library for Java, that provides immutable collection API. I am curious to know the reason for having an immutable Queue.

My understanding is that a Queue is used to produce data to it on one end and then a different thread consumes the data from the other end.

Immutable Queue doesn`t allow you to add data after its construction, then why would I use a Queue here.

Ideally, I would process a Queue as below, but for an immutable Queue, this goes into an infinite loop.

while(!queue.isEmpty()) {
    queue.dequeue(); // process elements in queue.
}

When I googled, all of the discussions are around how to implement an immutable queue, but doesn't explain the need for it.

Electroanalysis answered 15/7, 2017 at 4:42 Comment(4)
Good question, but seems better fit for SoftwareEngineering.Tricia
supposedly it can be more efficient: blogs.msdn.microsoft.com/ericlippert/2007/12/10/…Reuben
@Reuben Again the blog shows us an efficient implementation of an immutable queue. In fact, the Vavr's Queue consists of two separate lists for enqueued and dequeued elements. But, I still don't have the answer for my question: what problem does an immutable queue solves?Electroanalysis
@VinceEmigh when referring other sites, it is often helpful to point that cross-posting is frowned uponSabbatarian
E
13

My understanding is that a Queue is used to produce data to it on one end and then a different thread consumes the data from the other end.

A Queue is a FIFO (First-In-First-Out) data structure. It has many uses, other than as communication between threads.

I am curious to know the reason for having an immutable Queue.

If you are baffled by the need for an immutable anything, it seems that you don't understand functional programming. Remember, you said yourself that Vavr is a functional library, i.e. a library for writing functional code in Java.

One of the basic principles of functional programming is that everything is immutable.

This includes a Queue. If you need a queue, i.e. a FIFO collection, for storing your data, then it has to be immutable too.

As an example, let's say you wanted to add the numbers 1 to 10 to a queue, and then read from that queue and print the values.

In an imperative programming language like Java, you'd do that like this, using java.util.Queue and an implementation such as java.util.LinkedList:

// Build queue with numbers 1-10
Queue<Integer> queue = new LinkedList<>();
for (int i = 1; i <= 10; i++)
    queue.offer(i);

// Poll queue and print numbers
for (Integer num; (num = queue.poll()) != null; )
    System.out.println(num);

In contrast, functional programming relies heavily on recursive functions (hence functional programming), for operations like that, where nested call invocation on the stack has different values for the functions parameters.

Remember, in the imperative style, the counting variable i and the queue queue are both mutated during iteration.

In the functional style, they must both be immutable, so you do it by writing a recursive function like this (in Java), using io.vavr.collection.Queue:

private static Queue<Integer> build(int i, int end, Queue<Integer> queue) {
    if (i > end)
        return queue;
    return build(i + 1, end, queue.enqueue(i));
}

Then call it:

// Build queue with numbers 1-10
Queue<Integer> queue = build(1, 10, Queue.empty());

Since the queue is immutable, the enqueue() method returns a new queue with the new value added. That new queue is then passed as parameter on the recursive call, until done, at which point the final queue containing the numbers are returned back up the call stack.

Side-note: In a functional language, that implements tail-recursion optimization (Java doesn't), the build() function above will not actually build up a call stack, so it won't cause a stack overflow. Also, the new queue returned by enqueue() does not copy all existing values, so it's not as expensive as it sounds.

To then poll the values from the queue and print them, you'd also use a recursive method:

private static void print(Queue<Integer> queue) {
    if (queue.isEmpty())
        return;
    Tuple2<Integer,Queue<Integer>> t = queue.dequeue();
    System.out.println(t._1());
    print(t._2());
}

Here, dequeue() returns two values: the value removed from the queue, and the new queue with the value removed. The function then prints the value and makes a recursive call to print the rest of the queue.

Electroacoustics answered 15/7, 2017 at 6:14 Comment(9)
By specification, Queue impls aren't always FIFO: "Queues typically, but do not necessarily, order elements in a FIFO (first-in-first-out) manner. Among the exceptions are priority queues" (from docs). But I definitely see where it could play a role with something like vavr, encouraging stateless programming. +1, better than my answer IMO, more specific to the caseTricia
@VinceEmigh You're referring to the javadoc of Queue interface in Java. I'm talking about the abstract Queue, which is a pure FIFO structure. A PriorityQueue is not a FIFO structure.Electroacoustics
Yes, but the language tag is java, and specifications are important for integrity. That's what they chose for their specification, and it's practiced in the JCL. But like I said, my answer is generalized (in terms of Java), and may not apply to paradigm-specific requirements. Was just going by the specification, wouldn't want to violate contractsTricia
@VinceEmigh But the question is not about java.util.Queue. It's about io.vavr.collection.Queue, which has nothing in common with java.util.Queue, other than "Queue" name.Electroacoustics
If the idea is just to store a collection of elements and hand it over to another program to process them, then we could use a List or a Set. Why specifically Queue here? I can see the benefit of having an immutable List/Set though.Electroanalysis
@NareshBabu In functional programming, or the Vavr library at least, a List is a stack, i.e. a LIFO structure. This question is specifically about Queue, a FIFO structure. Question is about the Vavr functional programming library, and has nothing to do with the java.util Collection interfaces/classes. It is a question about functional programming, which is very different from the imperative programming Java programmers are used to.Electroacoustics
Can't argue with that. @NareshBabu List doesn't guarantee FIFO by spec, nor does Set. The requirements differTricia
Ok Now I wonder which data structure will be used in functional programming to achieve FIFO structure shared across multiple threads and the requirement is that more than one thread should not process the same element.Electroanalysis
@NareshBabu You obviously don't do it by using a Queue. :-) If you think about it, you realize that using a (blocking) queue to send items to one or more running consumer threads, is really just a way of having dedicated threads doing the work, threads that stay idle when queue is empty. Instead of using a queue, submitting Tasks to a thread pool will have the same effect. So, submitting tasks to an executor is the functional equivalent of sending items through a queue to a processing thread, and it's better too, since it allows the threads to do other things, if needed.Electroacoustics

© 2022 - 2024 — McMap. All rights reserved.