Functional programming has become more and more hot in recent years. Languages with functional programming despise languages without functional programming. Languages with pure functional programming despise imperfect functional programming languages.
So, what is functional programming, what is the core idea of functional programming?
The first feature of functional programming is that you can pass a function as a parameter to another function, a so-called higher-order function. For example, to sort an array, you can pass in a sort function as a parameter:
String[] array = { "orange", "Pear", "Apple" }; Arrays.sort(array, String::compareToIgnoreCase);
The second feature of functional programming is that you can return a function so that you can implement closures or lazy calculations:
The above two features are just a simplification of the code. In terms of code maintainability, the biggest advantage of functional programming is that the reference transparency, that is, the result of the function operation depends only on the input parameters, and does not depend on the external state. Therefore, we often say that functional programming has no side effects.
There is no huge side effect. There is no state inside the function, that is, the input is determined, the output is determined, and it is easy to test and maintain.
Many beginners tend to entangle “pure” functional languages, thinking that only Haskell, a language that eliminates variables and side effects, is authentic functional programming. Others even think that pure functions can’t have any IO operations, including logging.
In fact, this kind of tangling is meaningless, because the bottom layer of the computer is a system with completely variable memory and unpredictable input. It is unrealistic to pursue perfect without side effects. We only need to understand the idea of functional programming and do business logic. To “no side effects”, as for the insignificant “side effects” of variables, logging, and read caching, there is no need to worry about it, and there is almost no solution.
Let’s take a chestnut.
For example, a financial software requires a function to calculate personal income tax. The input is one IncomeRecord, and the output is a tax amount:
double calculateIncomeTax(IncomeRecord record) { ... }
Also assume that this is IncomeRecordlong:
class IncomeRecord { String id; String name; double salary; }
Let’s not consider the messy things of five insurances and one gold. We only pay attention to how to calculate taxes. For the sake of simplicity, we assume that 20% is calculated by directly deducting an exemption amount:
double calculateIncomeTax(IncomeRecord record) { double threshold = 3500; double tax = record.salary <= threshold ? 0 : (record.salary - threshold) * 0.2; return tax; }
The above procedure is no problem until September 1, 2018. The problem is that the threshold is adjusted to 5000 after September 1, 2018. In August 2018 and September 2018, the calculation results should be different. How to change?
Ordinary developer’s reform: Isn’t that simple? Get the current date directly and return the correct threshold:
double calculateIncomeTax(IncomeRecord record) { double threshold = today() < date(2018, 9, 1) ? 3500 : 5000; double tax = record.salary <= threshold ? 0 : (record.salary - threshold) * 0.2; return tax; }
The program is correct, the problem is:
The same input, running on August 31 and running on September 1st, the results are not the same, is it necessary for the accountant to do the August salary bar on September 1st, must first adjust the computer time to August?
Thinking about it from a functional programming perspective, you will find the problem:
today()This function returns the result in relation to time, which causes it to calculateIncomeTax()be no longer a pure function, it is related to the current time.
So how do you calculateIncomeTax()restore it to a pure function and support the starting point adjustment?
The method is to pass time-related variables as parameters, for example, to IncomeRecordadd a few fields:
class IncomeRecord { String id; String name; double salary; int year; int month; }
So we can eliminate today()the call:
double calculateIncomeTax(IncomeRecord record) { double threshold = date(record.year, record.month) < date(2018, 9) ? 3500 : 5000; double tax = record.salary <= threshold ? 0 : (record.salary - threshold) * 0.2; return tax; }
calculateIncomeTax()It has become a pure function, and accounting does not have to change the computer time.
Do you think this example is too simple? In fact, if a simple function can be written as stateful, then the complex business logic must be written into a pot of porridge.
Give a complicated chestnut:
For a stock trading system, if we define the input as cash and shareholding of all investors before the opening, and all orders during the trading session, then the output is the cash and shareholding of all shareholders after the close:
StockStatus process(StockStatus old, List<Order> orders) { ... for (Order order : orders) { ... sendExchangeResult(...); // Send a message to each transaction } ... }
Obviously, this is a pure function, although, in the process, this function will send a variety of heartbeat messages to shareholders.
If the model of the trading system is designed as such a pure function, then, in theory, we only need to process all the orders from the day the stock market is opened, and we can correctly get the status after the close today.
In other words, as long as the backup of the system state before the opening of any day (that is, the backup of the entire database) is taken, the order of the day is re-processed once, and the state of the closing of the day is obtained. This process can be done any number of times, and the results are unchanged, so it is very suitable for verifying that the modification of the code affects the business process.
Then the question comes, there are countless and time-related states in the trading system, how to deal with pure functions? The processing of this model is much more complicated than calculating taxes.
This is the essence of functional programming: the business system model is stateless. The quality of the model directly affects the correctness, reliability, stability of the code, and whether 996 is needed.
Discussion about this post