3 of 8 added 91 characters in body

I am admittedly biased as one applying such concepts in C++ by the language and its nature, as well as my domain, and even the way we use the language. But given these things, I think immutable designs are the least interesting aspect when it comes to reaping a bulk of the benefits associated with functional programming, like thread safety, ease of reasoning about the system, finding more reuse for functions (and finding we can combine them in any order without unpleasant surprises), etc.

Take this simplistic C++ example (admittedly not optimized for simplicity to avoid embarrassing myself in front of any image processing experts out there):

// Inputs an image and outputs a new one with the specified size.
Image resized_image(const Image& src, int new_w, int new_h)
     Image dst(new_w, new_h);
     for (int y=0; y < new_h; ++y)
         for (int x=0; x < new_w; ++x)
              dst[y][x] = src.sample(x / (float)new_w, y / (float)new_h);
     return dst;

While the implementation of that function mutates local (and temporary) state in the form of two counter variables and a temporary local image to output, it has no external side effects. It inputs an image and outputs a new one. We can multithread it to our hearts' content. It's easy to reason about, easy to thoroughly test. It's exception-safe since if anything throws, the new image is automatically discarded and we don't have to worry about rolling back external side effects (there are no external images being modified outside the function's scope, so to speak).

I see little to be gained, and potentially much to be lost, by making Image immutable in the above context, in C++, except to potentially make the above function more unwieldy to implement, and possibly a bit less efficient.


So pure functions (free of external side effects) are very interesting to me, and I emphasize the importance of favoring them often to team members even in C++. But immutable designs, applied just generally absent context and nuance, are not nearly as interesting to me since, given the imperative nature of the language, it's often useful and practical to be able to mutate some local temporary objects in the process of efficiently (both for developer and hardware) implementing a pure function.

Cheap Copying of Hefty Structures

The second most useful property I find is the ability cheaply copy the really hefty data structures around when the cost of doing so, as would often be incurred to make functions pure given their strict input/output nature, would be non-trivial. These wouldn't be small structures that can fit on the stack. They'd be big, hefty structures, like the entire Scene for a video game.

In that case the copying overhead could prevent opportunities for effective parallelism, because it might be difficult to parallelize physics and rendering effectively without locking if physics is mutating the scene that the renderer is simultaneously trying to draw, while simultaneously having physics copy the entire game scene around just to output one frame with physics applied might be equally ineffective. However, if the physics system was 'pure' in the sense that it merely inputted a scene and outputted a new one with physics applied, and such purity did not come at the cost of astronomical copying overhead, it could safely operate in parallel with the renderer without one waiting on the other.

So the ability to cheaply copy the really hefty data of your application state around and output new, modified versions with minimal cost to processing and memory use can really open up new doors for effective parallelism, and there I find lots of lessons to learn from how persistent data structures are implemented.


Finally there's immutability which I consider the least interesting of these three, but it can enforce, with an iron fist, when certain object designs are not meant to be used as local temporaries to a pure function, a valuable kind of "object-level purity", as in all methods no longer cause external side effects (no longer mutate member variables outside the immediate local scope of the method).

And while I consider it the least interesting of these three in languages like C++, it can certainly simplify the testing and thread-safety and reasoning of non-trivial objects. It can be a load off to work with the guarantee that an object cannot be given any unique state combination outside of its constructor, for example, and that we can freely pass it around, even by reference/pointer without leaning on constness and read-only iterators and handles and such, while guaranteeing (well, at least as much as we can within the language) that its original contents will not be mutated.

But I find this the least interesting property because most objects I see as beneficial as being used temporarily to implement a pure function, and I think immutability to the extremities in an immutable language is a rather counter-productive goal. I'd apply it sparingly for the parts of the codebase where it really helps the most.


[...] it would seem that persistent data structures are not in themselves sufficient to handle scenarios where one thread makes a change that is visible to other threads. For this, it seems we must use devices such as atoms, references, software transactional memory, or even classic locks and synchronization mechanisms.

Naturally if your design calls for modifications (in a user-end design sense) to be visible to multiple threads simultaneously as they occur, we're back to synchronization or at least the drawing board to work out some sophisticated ways to deal with this (I've seen some very elaborate examples used by experts dealing with these sorts of problems in functional programming).

But I have found, once you get that sort of copying and ability to output partially-modified versions of hefty structures dirt cheap, it does often open up lots of doors and opportunities you might not have thought about before to parallelize code that can run completely independently of each other.