Although it's sometimes expressed that way, functional programming¹ doesn't prevent stateful computations. What it does is force the programmer to make state explicit.
For example, let's take the basic structure of some program using an imperative queue (in some pseudolanguage):
q := Queue.new();
while (true) {
if (Queue.is_empty(q)) {
Queue.add(q, producer());
} else {
consumer(Queue.take(q));
}
}
The corresponding structure with a functional queue data structure (still in an imperative language, so as to tackle one difference at a time) would look like this:
q := Queue.empty;
while (true) {
if (q = Queue.empty) {
q := Queue.add(q, producer());
} else {
(tail, element) := Queue.take(q);
consumer(element);
q := tail;
}
}
Since the queue is now immutable, the object itself doesn't change. In this pseudo-code, q
itself is a variable; the assignments q := Queue.add(…)
and q := tail
make it point to a different object. The interface of the queue functions has changed: each must return the new queue object that results from the operation.
In a purely functional language, i.e. in a language with no side effect, you need to make all state explicit. Since the producer and consumer are presumably doing something, their state must be in their caller's interface here as well.
main_loop(q, other_state) {
if (q = Queue.empty) {
let (new_state, element) = producer(other_state);
main_loop(Queue.add(q, element), new_state);
} else {
let (tail, element) = Queue.take(q);
let new_state = consumer(other_state, element);
main_loop(tail, new_state);
}
}
main_loop(Queue.empty, initial_state)
Note how now every piece of state is explicitly managed. The queue manipulation functions take a queue as input and produce a new queue as output. The producer and consumer pass their state through as well.
Concurrent programming doesn't fit so well inside functional programming, but it fits very well around functional programming. The idea is to run a bunch of separate computation nodes and let them exchange messages. Each node runs a functional program, and its state changes as it sends and receives messages.
Continuing the example, since there's a single queue, it's managed by one particular node. Consumers send that node a message to obtain an element. Producers send that node a message to add an element.
main_loop(q) =
consumer->consume(q->take()) || q->add(producer->produce());
main_loop(q)
The one “industrialized” language that gets concurrency right³ is Erlang. Learning Erlang is definitely the path to enlightenment⁴ about concurrent programming.
Everybody switch to side-effect-free languages now!
¹ This term has several meanings; here I think you're using it to mean programming without side effects, and that's the meaning I'm also using.
² Programming with implicit state is imperative programming; object orientation is a completely orthogonal concern.
³ Inflammatory, I know, but I mean it. Threads with shared memory is the assembly language of concurrent programming. Message passing is a lot easier to understand, and the lack of side effects really shines as soon as you introduce concurrency.
⁴ And this is coming from someone who's not a fan of Erlang, but for other reasons.
The primary purpose of immutability is to ensure that there's no instant in time when the data in memory is in an invalid state. (The other is because mathematical notations are mostly static, and so immutable things are easier to conceptualize and model mathematically.) In memory, if another thread tries to read or write data while it's being worked with, it might end up going corrupt, or it might itself be in a corrupt state. If you have multiple assignment operations to an object's fields, in a multithreaded application, another thread might try to work with it sometime in between -- which could be bad.
Immutability remedies this by first writing all the changes to a new place in memory, and then doing the final assignment as one fell-swoop step of rewriting the pointer to the object to point to the new object -- which on all CPUs is an atomic operation.
Databases do the same thing using atomic transactions: when you start a transaction, it writes all the new updates to a new place on disk. When you finish the transaction, it changes the pointer on disk to where the new updates are -- which it does in a short instant during which other processes can't touch it.
This is also the exact same thing as your idea of creating new tables, except more automatic and more flexible.
So to answer your question, yes, immutability is good in databases, but no, you don't need to make separate tables just for that purpose; you can just use whatever atomic transaction commands are available for your database system.
Best Answer
Without immutability, you might have to pass an object around between different scopes, and you do not know beforehand if and when the object will be changed. So to avoid unwanted side effects, you start creating a full copy of the object "just in case" and pass that copy around, even if it turns out no property has to be changed at all. That will leave a lot more garbage than in your case.
What this demonstrates is - if you create the right hypothetical scenario, you can prove anything, especially when it comes to performance. My example, however, is not so hypothetical as it might sound. I worked last month on a program where we stumbled over exactly that problem because we initially decided against using an immutable data structure, and hesitated to refactor this later because it did not seem worth the hassle.
So when you look at cases like this one from an older SO post, the answer to your questions becomes probably clear - it depends. For some cases immutability will hurt performance, for some the opposite might be true, for lots of cases it will depend on how smart your implementation is, and for even more cases the difference will be negligible.
A final note: a real world problem you might encounter is you need to decide early for or against immutability for some basic data structures. Then you build a lot of code upon that, and several weeks or months later you will see if the decision was a good or a bad one.
My personal rule of thumb for this situation is:
For situations between these two extremes, use your judgement. But YMMV.