Original: https://bethallchurch.github.io/JavaScript-and-Functional-Programming/
Translation: http://www.zcfy.cc/article/1013
Translator Note: A translation is recommended, "functional Programming terminology analysis".
This is the note I made on July 29, 2016 when I listened to Kyle Simpson's wonderful course "Functional-light JavaScript" (plus personal in-depth research) (slide here).
Object-oriented has long dominated the JavaScript programming paradigm. However, recent interest in functional programming is growing. Functional programming is a programming style that emphasizes minimizing the number of changes in program state (that is, side effects [side effect]). Therefore, functional programming encourages the use of immutable data (immutable) and pure functions (pure functions) ("purely" means no side effects). It is also more inclined to use declarative style, encouraging the use of well-named functions, so that we can use the packaged details that are out of our sight and encode by describing what you want to happen.
Although there are some contradictions between object-oriented programming and functional programming, they are not mutually exclusive relationships. JavaScript has tools that can support both of these approaches. It can even be said that even if it is not used as a functional language in isolation, there are many concepts and best practices from functional methods that can help us make the code cleaner, more readable, and easier to infer.
Minimized side effects
The so-called side-effect refers to the internal function that produces a change beyond the function. Functions may do things such as manipulating the DOM, modifying variable values in higher-level scopes, or writing data to the database. These are the side effects.
Functions with side effects: Modifying variable values in higher-level scopes var x = 10;const MyFunc = function (y) { x = x + y;}; MyFunc (3); Console.log (x); 13myFunc (3); Console.log (x); 16
Side effects are not inherently evil. Programs that do not produce any side effects do not affect the world, and therefore do not make any sense (unless studied as a theoretical interest). However, the side effects are indeed dangerous and should be avoided as far as possible, unless absolutely necessary.
When a function has a side effect, it is not enough to identify what the function is doing, with the input and output. You must understand the context, the history of the state of the program, which makes the function more difficult to understand. In unpredictable interactions, side effects can lead to bugs, and functions are more difficult to test because of these dependencies.
Minimizing side effects is the most fundamental principle in functional programming, and most of the next sections can be seen as a summary of ways to avoid side effects.
View data as non-volatile (immutable)
Change (mutation) refers to the change in the original position of the value (an in-place changes to a value). Immutable values mean that once created, they will never change. In JavaScript, simple values such as numbers, strings, and Boolean values are immutable. However, data structures such as objects and arrays are mutable.
The push method changed the array const x = [1, 2];console.log (x); [1, 2]x.push (3); Console.log (x); [1, 2, 3]
Why avoid changing data?
Change is a side effect. The less things change in a program, the less you need to track records, and the simpler the program is.
The tools available in JavaScript to maintain the immutability of data structures such as objects and arrays are limited. The Object.freeze can be used to enforce immutable objects, but with only one level of depth of action:
Const Frozenobject = Object.freeze ({valueone:1, valuetwo: {nestedvalue:1}}); frozenobject.valueone = 2; FrozenObject.valueTwo.nestedValue = 2 not allowed; Let it be!
However, there are some great tool libraries that solve these problems, the most famous of which is the number of immutable.
For most applications, using a tool library to ensure immutability is somewhat overkill. In many cases, simply treating the data as immutable can benefit us a lot.
Avoid changes: arrays
The JavaScript array method can be generalized as a change method (Mutator methods) and a non-variable method. The method of change should be avoided wherever possible.
For example, the Concat method can be used instead of the push method. Push changes the original array; concat returns a new array consisting of the original array and the array as the parameters, and the original array is still intact.
Push changed the array const ARRAYONE = [1, 2, 3];arrayone.push (4); Console.log (Arrayone); [1, 2, 3, 4]//concat generates a new array, the original array remains constant const ARRAYTWO = [1, 2, 3];const arraythree = Arraytwo.concat ([4]); Console.log (A Rraytwo); [1, 2, 3]console.log (Arraythree); [1, 2, 3, 4]
There are also non-change methods, including map, filter, reduce, and so on.
Avoid changes: objects
You can use the Object.assign method instead of editing the object directly. This method copies the properties of the source object into the target object and returns the target object. If you always use an empty object as the target object, you can avoid editing the object directly by Object.assign.
Const Objectone = {Valueone:1};const objecttwo = {valuetwo:2};const Objectthree = Object.assign ({}, ObjectOne, OB Jecttwo); Console.log (Objectthree); {valueone:1, valuetwo:2}
About Const
Const is useful, but does not make the data immutable. It can only prevent variables from being re-assigned. This cannot be confused.
Const X = 1;x = 2; Const MYARRAY = [1, 2, 3];myarray = [0, 2, 3] are not allowed; Do not allow myarray[0] = 0; It's allowed!
Writing pure functions
Pure functions do not change the state of the program, nor do they produce perceptible side effects. The output of a pure function depends only on the input value. The return value is the same whenever and wherever it is called, as long as the input value is the same.
Pure functions are an important tool for minimizing side effects. In addition, context-independent features allow them to be highly testable and reusable.
In the code in the preceding side-effect section, the MyFunc function is a non-pure function, noting that the input is the same at two calls but is different each time the result is returned. However, it can also be written as a pure function:
Change global variable to local variable const MYFUNC = function (y) { const x = ten; return x + y;} Console.log (MyFunc (3)); 13console.log (MyFunc (3)); 13
Pass x as parameter const x = 10;const MyFunc = function (x, y) { return x + y;} Console.log (MyFunc (x, 3)); 13console.log (MyFunc (x, 3)); 13
Your program will certainly end up with some side effects. When side effects arise, be careful to constrain and limit their effects as much as possible.
Write functions that return functions (function-generating Functions)
Find someone who has experience and let them guess what the following code does:
Example 1
Const NUMBERS = [1, 2, 3];for (Let i = 0; i < numbers.length; i++) { console.log (numbers[i]);}
Example 2
Const NUMBERS = [1, 2, 3];const print = function (input) { console.log (input);}; Numbers.foreach (print);
Everyone I tested had better luck in Example 2. Example 1 shows a command-style method that prints a list of numbers. Example 2 shows a declarative approach. Looping through an array, printing a number in the console, these details are packaged in a ForEach and print function, and you don't need to know what to do to express what we need the program to do. This makes the code more readable. The last line of Example 2 looks very close to English sentences.
With this approach, it involves writing a number of functions. By using existing functions to write functions that generate new functions, you can make the process less repetitive (dry-er).
In particular, the two features of JavaScript make this form of function generation possible. The first one is closures. The function can access the variable in the containing scope, even if the scope no longer exists, this is the closure. The second feature is that JavaScript treats functions as values. This makes it possible to write higher-order functions, which can receive functions as parameters and/or return functions.
Together, we can write functions that return functions. The returned function can "remember" the arguments passed to the generated function and use them elsewhere in the program.
function combination
By combining functions, functions may be combined to form new functions. Take a look at examples:
Generate Addthensquareconst Add = function (x, y) {return x + y by combination of add and square functions ;}; Const SQUARE = function (x) { return x * x;}; Const ADDTHENSQUARE = function (x, y) { return square (Add (x, y));};
You may find that you have been repeating this form of using smaller functions to generate a more complex function. It is often more efficient to write a combination function:
Const ADD = function (x, y) { return x + y;}; Const SQUARE = function (x) { return x * x;}; Const COMPOSETWO = function (f, g) { return function (x, y) { return G (f (x, y));}; }; Const ADDTHENSQUARE = composetwo (add, square);
You can also go farther and write a more generalized combination of functions:
This version of the Composetwo initialization function can receive any number of parameters const COMPOSETWO = function (f, g) { return function (... args) { return G (f (... args)); };};/ /Composemany can receive any number of functions//Their initialization function can receive any number of parameters const Composemany = function (... args) { const FUNCS = args; return function (... args) { Funcs.foreach (func) = { args = [Func.apply (this, args)]; }); return args[0];};
The final form of the combined function depends on the level of commonality you need and the type of API you prefer.
Partial function (partial application)
The partial function specifies one or more parameters, and then returns another function, which is then fully called.
In the following example, double, triple, and quadruple are the partial functions of the multiply function.
Const MULTIPLY = function (x, y) { return x * y;}; Const PARTAPPLY = function (FN, x) { return function (y) { fn (x, y); };}; Const DOUBLE = partapply (multiply, 2); const TRIPLE = Partapply (multiply, 3); const QUADRUPLE = partapply (multiply, 4) ;
Currying
Curry is the process of converting a function that receives multiple parameters into a series of functions that receive only one parameter.
Const MULTIPLY = function (x, y) { return x * y;}; Const CURRY = function (fn) { return function (x) { return function (y) { return fn (x, y); }; };}; Const CURRIEDMULTIPLY = Curry (multiply); const DOUBLE = curriedmultiply (2); const TRIPLE = curriedmultiply (3); const QU Adruple = curriedmultiply (4); Console.log (triple (6)); 18
The curry and partial functions are conceptually similar (probably not all two of them need to be used), but they are still different. The main difference is that currying always generates a function chain, receiving only one parameter at a time, while the function returned by the partial function can receive multiple arguments at a time. This difference is clearer when comparing the functions that they act on to a minimum of three parameters:
Const MULTIPLY = function (x, y, z) { return x * y * z;}; Const CURRY = function (fn) { return function (x) { return function (y) { return function (z) { re TURN fn (x, y, z);};};}; Const PARTAPPLY = function (FN, x) { return function (Y, z) { return fn (x, y, z); };}; Const CURRIEDMULTIPLY = Curry (multiply); Const partiallyappliedmultiply = partapply (multiply); Console.log (curriedm Ultiply (10) (5) (2)); 100console.log (Partiallyappliedmultiply (5, 2)); 100
Recursive
A recursive function is a function that calls itself until the basic conditions are met. Recursive functions are highly declarative. They are also very elegant, it is very cool to write!
The following is an example of calculating the factorial of a recursive calculation:
Const FACTORIAL = function (n) { if (n = = 0) { return 1; } return n * factorial (n-1);}; Console.log (factorial (10)); 3628800
Using recursive functions in JavaScript needs to be more careful. Each time a function call adds a new call frame to the call stack, the call frame pops up from the call stack when the function returns. Recursive function calls call itself before returning, so it is easy to go beyond the limit of the call stack, causing the program to crash.
However, this can be avoided by tail call optimizations.
Tail call optimization
A tail call means that the last step of a function is to call a function. Tail call optimization refers to the same call frame that is reused when the language compiler recognizes a tail call. This means that when you write a recursive function for a tail call, the limit of the calling frame is never exceeded because the calling frame is reused.
The following is an example of rewriting the previous recursive function with the tail recursion optimization:
Const FACTORIAL = function (n, base) { if (n = = 0) { return base; } Base *= N; return factorial (n-1, base);}; Console.log (Factorial (10, 1)); 3628800
JavaScript and functional programming