I have encountered with such statement:
"Programming in a functional style makes the state presented to your code explicit, which makes it much easier to reason about, and, in a completely pure system, makes thread race conditions impossible."
I see this point of view, but how can I achieve this in real-world problems?
For example:
There is a functional program with two functions:
def getMoney(actMoney: Integer, moneyToGet: Integer): Integer
= actMoney - moneyToGet
def putMoney(actMoney: Integer, moneyToPut: Integer): Integer
= actMoney + moneyToPut
Then, I really would like to define functions getActualMoney and saveActualMoney for a given Account, but I can't, they are not pure. That's because I get Money for a given Account from some memory and I save Money for a given Account to some memory (there is state).
def getActualMoney(accountNo: String): Integer = {...}
def saveActualMoney(accountNo: String, actMoney: Integer): Unit = {...}
So I have to get my current Money from "outside". And let's say, that my program is working in such way. Now I have two simultaneous requests, first: get some money, second put some money for the same account. Of course I will get two different results. So there is a race condition.
I understand, that I should make a transaction on this account "outside" programming code. So that, such situation should not have happened. For a better concurrency, functions should look like that:
def getMoney(
acountNo: String,
actMoney: Integer,
moneyToGet: Integer): (String, Integer)
= (acountNo, actMoney - moneyToGet)
def putMoney(
acountNo: String,
actMoney: Integer,
moneyToPut: Integer): (String, Integer)
= (acountNo, actMoney + moneyToPut)
Is it what is going about? Is it worth doing?
A simple example of a race condition is a light switch. In some homes, there are multiple light switches connected to a common ceiling light. When these types of circuits are used, the switch position becomes irrelevant. If the light is on, moving either switch from its current position turns the light off.
Static, dynamic, and essential forms A static race condition occurs when a signal and its complement are combined. A dynamic race condition occurs when it results in multiple transitions when only one is intended. They are due to interaction between gates.
"Programming in a functional style makes the state presented to your code explicit, which makes it much easier to reason about, and, in a completely pure system, makes thread race conditions impossible."
While the quote is technically correct, in that you cannot have race conditions in purely functional code, you do indeed need to have side-effects in order for a program to do anything useful other than calculate a value, and so it's somewhat disingenuous, though at the same time it does speak to a useful distinction.
Programming in a style that minimizes side-effects and maximizes purely functional code will reduce the area of code where race conditions can happen to those places where you know you are explicitly using side-effects, and that is often the benefit touted by purely functional programming advocates.
For your example: if the accounts with money are just simulations that only live inside your program, it'd be easy to keep those as pure functions, by having each operation on an account return a new account object with the updated values. If you do it that way, you will get the benefits of purely functional code in general: being able to reason about referentially transparent code by using "equational reasoning", which is where you can assume any call to a function is equivalent to the value that the function returns given the parameters it's passed, regardless of how many times you call it in what order. (this to answer your question of "is it worth doing?")
But if you're talking about a system that, say, stores accounts and balances in an external database, then you definitely need side-effects and you will have to be concerned about the order of operations and potential race conditions just as you would in any other language. By isolating your side-effects, though, you can be much more sure about where you do have to care about such concurrency issues, and also make use of higher-level concurrency abstractions such as MVars, IORefs, or STM (available in Haskell and, seemingly, Scala), which encourage using pure operations on impure data -- keeping things isolated and composable.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With