What is the need for an immutable queue?

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

My understanding is that Queue is used to create data at one end and then another thread is consuming data from the other end.

An immutable queue does not allow you to add data after it has been built, so why would I use a queue here.

Ideally, I would process the queue like 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 the discussions around how to implement an immutable queue, but don't explain the need for it.

+5


source to share


2 answers


  As I understand it, a queue is used to produce data at one end and then another thread is consuming data from the other end.

A queue is a FIFO (first-first-first) data structure. It has many uses other than communication between threads.

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

If you are confused about the need for something immutable, you don't seem to understand functional programming . Remember, you said yourself that Vavr is a functional library, that is, a library for writing functional code in Java.

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

This includes the queue. If you need a queue to store your data, that is, a collection of FIFOs, that must also be immutable.

For example, suppose you want to add numbers from 1 to 10 to a queue and then read from that queue and print the values.

In an imperative programming language like Java, you would do it like this, using java.util.Queue

and implementation like 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 similar operations where the call to a nested call on the stack has different meanings for function parameters.



Remember, in an imperative style, the count variable i

and queue queue

change during iteration.

In functional style, they both should 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 method enqueue()

returns a new queue with the new value appended. This new queue is then passed as a parameter to the recursive call until it completes, after which the last queue containing the numbers is pushed back onto the call stack.

Note: in a functional language that implements tail recursion optimization (Java does not), the above function build()

will not actually create a call stack, so it will not cause a stack overflow. Also, the new queue returned enqueue()

does not copy all existing values, so it is not as expensive as it sounds.

To then poll for values ​​from the queue and print them, you must also use the 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 it 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.

+10


source


Not necessary. This is not practical. It is common knowledge that people ask him when he is mentioned. 1 , 2

From doc :

A collection for storing items before processing. In addition to basic collection operations, queues provide additional insert, retrieval, and validation operations.

These operations are element()

/ peek()

, poll()

/ remove()

and add(E)

/ offer(E)

respectively.

Extract and check are mutation operations, so we're left with element()

and peak()

. These operations work on the header of the queue, making the following entries unavailable through the Queue

-specific behavior.



The element () and peek () methods return, but do not remove, the queue header.


My understanding is that Queue is used to create data at one end and then another thread is consuming data from the other end.

You usually use BlockingQueue

for something like this. The spec Queue

explicitly states:

The queue interface does not define queue blocking methods that are common in concurrent programming. These methods, which wait for the elements to appear or to access them, are defined in the BlockingQueue interface, which extends this interface.

+1


source







All Articles