I recently asked a question about functional programming, and received (good!) answers that prompted more questions (as seems to be the case with learning, sometimes). Here are a couple examples:
-
One answer made reference to an advantage of immutable data structures: each thread can have its own copy. Now, to me, this sounds rather like a version control system (to use an analogy), where instead of locking code that someone has checked out so that it can’t be modified by anyone else, everyone can check out their own copies. Sounds good. However, in VCS you have the concept of ‘merging’ changes, in the case that two people changed the same stuff. It seems like this issue could certainly come up in a multithreaded scenario… so how is ‘merging’ done when it’s important that threads see the most recent data?
-
This answer talked about the case where operations were being performed in a loop on an object, and how you can use a new object each time through instead of updating an old one. However, let’s say the
bankAccountis being updated in a non-loop scenario–for example a GUI banking system. The operator clicks the ‘Change Interest Rate’ button, which fires an event that would (in C# for example) do something likebankAccount.InterestRate = newRateFromUser. I feel like I’m being dense here, but hopefully my example makes sense: there has to be some way that the object is updated, right? Several other things may depend on the the new data.
Anyway, if you can help me get my head around the paradigm shift, I’d be appreciative. I remember my brain going through similar ‘stupid phases’ when learning OOP after a background of the simple procedural imperative approach to coding.
Answer to part 1: Immutable objects don’t by themselves support anything like ‘merging’ to allow the results of two thread’s updates to be combined. There are two major strategies for that: the pessimistic and the optimistic. If you’re pessimistic, you assume it’s quite likely that two threads will want to update the same piece of data at the same time. So you employ locking, such that the second thread will freeze up until the first thread says it has finished. If you’re optimistic that this will happen only rarely, you let both threads work on their own logical copy of the data. The one that finishes first supplies the new version, and the other has to start again from the beginning – only now it starts from the results of the first thread’s changes. This expensive restarting only happens occasionally though, so over all it performs better due to the lack of locking (although this is only true if your optimism was well placed about how rarely a clash occurs).
Part 2: Pure functional stateless languages do not really eliminate that problem. Even a pure Haskell program can have state associated with it. The difference is that stateful code has a different return type. A function that manipulates state is expressed as a sequence of operations that operate on an object representing that state. In an absurd example, consider the computer’s file system. Every time a program modifies the contents of a file (even by a single byte) it has created a new ‘version’ of the entire file system. And by extension, a new version of the entire universe. But let’s focus on the file system for now. Any other part of the program that examines the file system may now be affected by that modified byte. So Haskell says that functions operating on the file system must effectively pass around an object representing a version of the file system. Then because this would be tedious to manually deal with, it turns the requirement inside out and says that if a function wants to be able to do IO, it must return a sort of container object. Inside the container is the value the function wants to return. But the container serves as evidence that the function also either has side-effects or can see side-effects. It means that Haskell’s type system is able to distinguish between functions-with-side-effects and ‘pure’ functions. So it helps to contain and manage the statefulness of code, without really eliminating it.