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.

Naresh Babu
  • 702
  • 1
  • 7
  • 17
  • Good question, but seems better fit for [SoftwareEngineering](https://softwareengineering.stackexchange.com). – Vince Jul 15 '17 at 04:55
  • supposedly it can be more efficient: https://blogs.msdn.microsoft.com/ericlippert/2007/12/10/immutability-in-c-part-four-an-immutable-queue/ – tima Jul 15 '17 at 05:23
  • @tima 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? – Naresh Babu Jul 15 '17 at 06:13
  • @VinceEmigh when referring other sites, it is often helpful to point that [cross-posting is frowned upon](https://meta.stackexchange.com/tags/cross-posting/info) – gnat Jul 15 '17 at 08:16

1 Answers1

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.

Andreas
  • 154,647
  • 11
  • 152
  • 247
  • 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 case – Vince Jul 15 '17 at 06:19
  • 1
    @VinceEmigh You're referring to the javadoc of [`Queue`](https://docs.oracle.com/javase/8/docs/api/java/util/Queue.html) interface in Java. I'm talking about the abstract [**Queue**](https://en.wikipedia.org/wiki/Queue_(abstract_data_type)), which is a pure FIFO structure. A [`PriorityQueue`](https://docs.oracle.com/javase/8/docs/api/java/util/PriorityQueue.html) is not a FIFO structure. – Andreas Jul 15 '17 at 06:22
  • 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 contracts – Vince Jul 15 '17 at 06:26
  • @VinceEmigh But the question is *not* about [`java.util.Queue`](https://docs.oracle.com/javase/8/docs/api/java/util/Queue.html). It's about [`io.vavr.collection.Queue`](https://static.javadoc.io/io.vavr/vavr/0.9.0/io/vavr/collection/Queue.html), which has nothing in common with `java.util.Queue`, other than "Queue" name. – Andreas Jul 15 '17 at 06:28
  • 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. – Naresh Babu Jul 15 '17 at 06:28
  • @NareshBabu In functional programming, or the Vavr library at least, a [`List`](https://static.javadoc.io/io.vavr/vavr/0.9.0/io/vavr/collection/List.html) 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. – Andreas Jul 15 '17 at 06:32
  • Can't argue with that. @NareshBabu [`List`](https://static.javadoc.io/io.vavr/vavr/0.9.0/io/vavr/collection/List.html) doesn't guarantee FIFO by spec, nor does [`Set`](https://static.javadoc.io/io.vavr/vavr/0.9.0/io/vavr/collection/Set.html). The requirements differ – Vince Jul 15 '17 at 06:34
  • 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. – Naresh Babu Jul 15 '17 at 07:02
  • @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. – Andreas Jul 15 '17 at 12:16