Remember: functional programming is not programming with functions! ! !
23.4 Functional Programming
23.4.1 What is functional programming?
What is functional programming? If you ask so bluntly, you will find that it is a concept that is not easy to explain. Many veterans with many years of experience in the field of programming cannot clearly explain what functional programming is studying. Functional programming is indeed an unfamiliar field for programmers who are familiar with procedural programming. The concepts of closure, continuation, and currying seem so unfamiliar to us. The familiar if, else, and while have nothing in common. Although functional programming has beautiful mathematical prototypes that procedural programming cannot match, it is so mysterious that only those with a PhD can master it.
Tip: This section is a bit difficult, but it is not a necessary skill to master JavaScript. If you don’t want to use JavaScript to complete the tasks that are done in Lisp, or don’t want to learn the esoteric skills of functional programming, you can skip it. Go through them and enter the next chapter of your journey.
So back to the question, what is functional programming? The answer is long…
The first law of functional programming: Functions are of first type.
How should this sentence itself be understood? What is a true Type One? Let’s look at the following mathematical concepts:
binary equation F(x, y) = 0, x, y are variables, write it as y = f(x), x is a parameter, y is the return value, f is from x to y The mapping relationship is called a function. If there is, G(x, y, z) = 0, or z = g(x, y), g is the mapping relationship from x, y to z, and is also a function. If the parameters x and y of g satisfy the previous relationship y = f(x), then we get z = g(x, y) = g(x, f(x)). This has two meanings. One is f( x) is a function on x and a parameter of function g. Secondly, g is a higher-order function than f.
In this way, we use z = g(x, f(x)) to represent the associated solution of the equations F(x, y) = 0 and G(x, y, z) = 0, which is an iterative function. We can also express g in another form, remember z = g(x, y, f), so that we generalize the function g into a higher-order function. Compared with the previous one, the advantage of the latter representation is that it is a more general model, such as the associated solution of T(x,y) = 0 and G(x,y,z) = 0. We also It can be expressed in the same form (just let f=t). In this language system that supports the iteration of converting the solution to a problem into a higher-order function, the function is called "first type".
Functions in JavaScript are clearly "first type". Here is a typical example:
Array.prototype.each = function(closure)
{
return this.length ? [closure(this[0])].concat(this.slice(1).each(closure)) : [];
}
This is really a magical magic code, which gives full play to the charm of functional style. There are only functions and symbols in the entire code. It's simple in form and infinitely powerful.
[1,2,3,4].each(function(x){return x * 2}) gets [2,4,6,8], while [1,2,3,4].each(function(x ){return x-1}) gets [0,1,2,3].
The essence of functional and object-oriented is that "Tao follows nature". If object-oriented is a simulation of the real world, then functional expression is a simulation of the mathematical world. In a sense, its level of abstraction is higher than object-oriented, because mathematical systems inherently have features that are incomparable in nature. of abstraction.
The second law of functional programming: Closures are functional programming’s best friend.
Closures, as we have explained in previous chapters, are very important for functional programming. Its biggest feature is that you can directly access the outer environment from the inner layer without passing variables (symbols). This brings great convenience to functional programs under multiple nesting. Here is an example :
(function outerFun(x)
{
return function innerFun(y)
{
return x * y;
}
})(2)(3);
The third law of functional programming: functions can be Currying.
What is Currying? It's an interesting concept. Let’s start with mathematics: we say, consider a three-dimensional space equation F(x, y, z) = 0, if we limit z = 0, then we get F(x, y, 0) = 0, denoted as F'(x, y). Here F' is obviously a new equation, which represents the two-dimensional projection of the three-dimensional space curve F(x, y, z) on the z = 0 plane. Denote y = f(x, z), let z = 0, we get y = f(x, 0), denote it as y = f'(x), we say that the function f' is a Currying solution of f.
An example of JavaScript Currying is given below:
function add(x, y)
{
if(x!=null && y!=null) return x + y;
else if(x!=null && y==null) return function(y)
{
return x + y;
}
else if(x==null && y!=null) return function(x)
{
return x + y;
}
}
var a = add(3, 4);
var b = add(2);
var c = b(10);
In the above example, b=add(2) results in a Currying function of add(), which is a function of parameter y when x = 2. Note that it is also used above. Properties of closures.
Interestingly, we can generalize Currying for any function, for example:
function Foo(x, y, z, w)
{
var args = arguments;
if(Foo.length < args.length)
return function()
{
return
args.callee.apply(Array.apply([], args).concat(Array.apply([], arguments)));
}
else
return x + y – z * w;
}
The fourth law of functional programming: delayed evaluation and continuation.
//TODO: Think about it again here
23.4.2 Advantages of Functional Programming
Unit Testing
Every symbol of strict functional programming is a reference to a direct quantity or expression result, and no function has side effects. Because the value is never modified somewhere, and no function modifies a quantity outside its scope that is used by other functions (such as class members or global variables). This means that the result of a function evaluation is only its return value, and the only things that affect its return value are the function's parameters.
This is a unit tester's wet dream. For each function in the program under test, you only need to care about its parameters, without having to consider the order of function calls, or carefully setting external state. All you have to do is pass parameters that represent edge cases. If every function in the program passes unit testing, you have considerable confidence in the quality of the software. But imperative programming cannot be so optimistic. In Java or C++, it is not enough to just check the return value of a function - we must also verify the external state that the function may have modified.
Debugging
If a functional program doesn't behave the way you expect, debugging is a piece of cake. Because bugs in functional programs don't depend on code paths unrelated to them prior to execution, problems you encounter can always be reproduced. In imperative programs, bugs appear and disappear, because the function of the function depends on the side effects of other functions, and you may search for a long time in directions unrelated to the occurrence of the bug, but without any results. This is not the case with functional programs - if the result of a function is wrong, then no matter what else you execute before, the function will always return the same wrong result.
Once you recreate the problem, finding its root cause will be effortless and may even make you happy. Interrupt the execution of that program and examine the stack. As with imperative programming, the parameters of each function call on the stack are presented to you. But in imperative programs these parameters are not enough. Functions also depend on member variables, global variables and the state of the class (which in turn depends on many of these). In functional programming, a function only depends on its parameters, and that information is right under your eyes! Also, in an imperative program, just checking the return value of a function cannot make you sure that the function is working properly. You have to check the status of dozens of objects outside the scope of that function to confirm. With a functional program, all you have to do is look at its return value!
Check the parameters and return values of the function along the stack. As soon as you find an unreasonable result, enter that function and follow it step by step. Repeat this process until you find the point where the bug is generated.
Parallel functional programs can be executed in parallel without any modification. Don't worry about deadlocks and critical sections because you never use locks! No data in a functional program is modified twice by the same thread, let alone two different threads. This means that threads can be simply added without a second thought without causing the traditional problems that plague parallel applications.
If this is the case, why doesn't everyone use functional programming in applications that require highly parallel operations? Well, they are doing that. Ericsson designed a functional language called Erlang and used it in telecommunications switches that require extremely high fault tolerance and scalability. Many people have also discovered the advantages of Erlang and started using it. We're talking about telecommunications control systems, which require far more reliability and scalability than a typical system designed for Wall Street. In fact, the Erlang system is not reliable and extensible, JavaScript is. Erlang systems are just rock solid.
The story about parallelism doesn't stop there. Even if your program is single-threaded, a functional program compiler can still optimize it to run on multiple CPUs. Please look at the following code:
String s1 = somewhatLongOperation1();
String s2 = somewhatLongOperation2();
String s3 = concatenate(s1, s2);
In a functional programming language, the compiler analyzes the code to identify potentially time-consuming functions that create strings s1 and s2, and then runs them in parallel. This is not possible in imperative languages, where each function may modify state outside the scope of the function and subsequent functions may depend on these modifications. In functional languages, automatically analyzing functions and identifying candidates suitable for parallel execution is as simple as automatic function inlining! In this sense, functional-style programming is "future proof" (even if I don't like to use industry terms, I will make an exception this time). Hardware manufacturers could no longer make CPUs run faster, so they increased the speed of processor cores and achieved a fourfold speed increase due to parallelism. Of course, they also forgot to mention that the extra money we spent was only used on software to solve parallel problems. A small proportion of imperative software and 100% functional software can run directly in parallel on these machines.
Hot deployment of code
used to require installing updates on Windows, and restarting the computer was unavoidable, and more than once, even if a new version of the media player was installed. Windows XP greatly improved this situation, but it's still not ideal (I ran Windows Update at work today, and now an annoying icon always shows up in the tray unless I reboot the machine). Unix systems have always run in a better mode. When installing updates, only system-related components need to be stopped, rather than the entire operating system. Even so, this is still unsatisfactory for a large-scale server application. Telecommunications systems must be operational 100% of the time because if emergency dialing fails while the system is being updated, loss of life could result. There's no reason Wall Street firms have to shut down services over the weekend to install updates.
The ideal situation is to update the relevant code without stopping any components of the system at all. This is impossible in an imperative world. Consider that when the runtime uploads a Java class and overrides a new definition, then all instances of this class will be unavailable because their saved state is lost. We could start writing some tedious version control code to solve this problem, then serialize all instances of this class, destroy these instances, then recreate these instances with the new definition of this class, and then load the previously serialized data and hope that the loading code will properly port that data to the new instance. On top of this, the porting code must be manually rewritten for each update, and great care must be taken to prevent breaking the interrelationships between objects. The theory is simple, but practice is not easy.
For functional programs, all state, i.e. the parameters passed to the function, are saved on the stack, making hot deployment a breeze! In fact, all we need to do is do a diff between the working code and the new version, and then deploy the new code. The rest will be done automatically by a language tool! If you think this is a science fiction story, think again. For years Erlang engineers have been updating their running systems without interrupting it.
Machine-Assisted Reasoning and Optimization
An interesting property of functional languages is that they can be reasoned with mathematically. Because a functional language is just an implementation of a formal system, all operations done on paper can be applied to programs written in this language. Compilers can use mathematical theory to transform a piece of code into equivalent but more efficient code [7]. Relational databases have been undergoing this type of optimization for years. There's no reason why this technique can't be applied to regular software.
Additionally, you can use these techniques to prove parts of your program are correct, and maybe even create tools to analyze your code and automatically generate edge cases for unit testing! This functionality is of no value to a robust system, but if you are designing a pace maker or an air traffic control system, this tool is indispensable. If the applications you write are not core tasks in the industry, this type of tool can also give you a trump card over your competitors.
23.4.3 Disadvantages of functional programming
Side effects of closures
In non-strict functional programming, closures can override the external environment (we have already seen it in the previous chapter), which brings side effects, and when such side effects occur frequently And when the environment in which the program runs is frequently changed, errors become difficult to track.
//TODO:
recursive form
Although recursion is often the most concise form of expression, it is not as intuitive as non-recursive loops.
//TODO:
The weakness of delayed value
//TODO: