In-depth exploration of javascript functional programming, in-depth exploration of javascript
Sometimes, an elegant implementation is a function. Not a method. Not a class. Not a framework. It's just a function. -John Carmack, chief programmer of the game "Destroyer"
Functional programming is all about how to break down a problem into a series of functions. In general, functions are chained, nested, and passed back and forth. They are regarded as first-class citizens. If you have used frameworks such as jQuery or Node. js, you should have used such technologies, but you are not aware of them.
We started with a small embarrassment in Javascript.
Suppose we need a list of values, which are assigned to common objects. These objects may contain anything: data, HTML objects, and so on.
var obj1 = {value: 1}, obj2 = {value: 2}, obj3 = {value: 3};var values = [];function accumulate(obj) { values.push(obj.value);}accumulate(obj1);accumulate(obj2);console.log(values); // Output: [obj1.value, obj2.value]
This code is usable but unstable. Any code can change the values object without using the accumulate () function. And if we forget to assign an array [] to values, this code will not work.
However, if the variable is declared inside the function, it will not be changed by any tricky code.
function accumulate2(obj) { var values = []; values.push(obj.value); return values;}console.log(accumulate2(obj1)); // Returns: [obj1.value]console.log(accumulate2(obj2)); // Returns: [obj2.value]console.log(accumulate2(obj3)); // Returns: [obj3.value]
No! Only the value of the last input object is returned.
We may be able to solve this problem by embedding a function in the first function.
var ValueAccumulator = function(obj) { var values = [] var accumulate = function() { values.push(obj.value); }; accumulate(); return values;};
However, the problem persists, and we cannot access the accumulate function and the values variable now.
What we need is a self-called function.
Self-called functions and closures
What if we can return a function expression that can return the values array in turn? Variables declared in a function can be accessed by all code in the function, including self-called functions.
By using a self-called function, the preceding embarrassment disappears.
var ValueAccumulator = function() { var values = []; var accumulate = function(obj) { if (obj) { values.push(obj.value); return values; } else { return values; } }; return accumulate;};//This allows us to do this:var accumulator = ValueAccumulator();accumulator(obj1);accumulator(obj2);console.log(accumulator());// Output: [obj1.value, obj2.value]ValueAccumulator = -> values = [] (obj) -> values.push obj.value if obj values
These are all about the scope. The variable values is visible in the internal function accumulate (), even when external code calls this function. This is called a closure.
The closure in Javascript means that the function can access the parent scope, even if the execution of the parent function is complete.
Closure is a feature of all functional languages. Traditional imperative languages do not have closures.
High-order functions
Self-called functions are actually a form of high-order functions. A high-order function is a function that uses other functions as input or returns a function as output.
High-order functions are not common in traditional programming. When imperative programmers use loops to iterate arrays, functional programmers use a completely different implementation method. Through higher-order functions, each element in the array can be applied to a function and a new array is returned.
This is the central idea of functional programming. High-order functions can pass logic to functions like objects.
In Javascript, functions are treated as first-class citizens, which is the same as classical functions such as Scheme and Haskell. This may sound a little weird. Actually, it means that a function is treated as a basic type, just like a number or an object. If numbers and objects can be passed back and forth, the function can also.
Let's take a look. Use the ValueAccumulator () function in the previous section with the higher-order function:
// Use forEach () to traverse an array and call the callback function accumulator2 for each of its elements
Var accumulator2 = ValueAccumulator ();
Var objects = [obj1, obj2, obj3]; // This array can be large.
Objects. forEach (accumulator2 );
Console. log (accumulator2 ());
Pure Function
The calculation results returned by pure functions are only related to the input parameters. External variables and global statuses are not used here, and there are no side effects. In other words, the input variables cannot be changed. Therefore, only values returned by pure functions can be used in the program.
Use mathematical functions to give a simple example. Math. sqrt (4) will always return 2 without any hidden information, such as settings or status, and will not cause any side effects.
Pure functions are the true interpretations of mathematical "functions", that is, the relationship between input and output. Their ideas are simple and easy to reuse. Since pure functions are completely independent, they are more suitable for use once and again.
Examples are provided to compare non-pure functions and pure functions.
// The function var printCenter = function (str) {var elem = document. createElement ("div"); elem. textContent = str; elem. style. position = 'absolute '; elem. style. top = window. innerHeight/2 + "px"; elem. style. left = window. innerWidth/2 + "px"; document. body. appendChild (elem) ;}; printCenter ('Hello World'); // pure function to accomplish the same thing var printSomewhere = function (str, height, width) {var elem = document. createElement ("div"); elem. textContent = str; elem. style. position = 'absolute '; elem. style. top = height; elem. style. left = width; return elem;}; document. body. appendChild (printSomewhere ('Hello world', window. innerHeight/2) + 10 + "px", window. innerWidth/2) + 10 + "px") rows );
Non-pure functions depend on the state of the window object to calculate the width and height. Self-contained pure functions require these values to be passed in as parameters. In fact, it allows information to be printed anywhere, which makes this function more versatile.
A non-pure function seems to be easier to choose because it implements append elements within itself, rather than returning elements. The pure function printSomewhere () that returns the value is better in combination with other functional programming techniques.
var messages = ['Hi', 'Hello', 'Sup', 'Hey', 'Hola'];messages.map(function(s, i) { return printSomewhere(s, 100 * i * 10, 100 * i * 10);}).forEach(function(element) { document.body.appendChild(element);});
When a function is pure, that is, it does not depend on the State and environment, we don't have to worry about when it is actually computed. This will be discussed in the subsequent inertia evaluation.
Anonymous Functions
Another benefit of using a function as a first-class object is that it is an anonymous function.
As the name implies, an anonymous function is a function without a name. Actually more than that. It allows the ability to define temporary logic on site. This usually brings convenience: If a function is used only once, there is no need to waste a variable name for it.
Here are some examples of anonymous functions:
// Standard function () {return "hello world"} for writing anonymous functions; // an anonymous function can be assigned to the variable var anon = function (x, y) {return x + y}; // The anonymous function is used to replace the named callback function. This is a more common use of the anonymous function setInterval (function () {console. log (new Date (). getTime ()}, 1000); // Output: 1413249010672,141 3249038573, 1413249010674 ,... // if it is not included in an anonymous function, it will be immediately executed, // and an undefined will be returned as the callback function: setInterval (console. log (new Date (). getTime (), 1000) // Output: 1413249010671
The following is an example of the combination of anonymous and high-level functions.
function powersOf(x) { return function(y) { // this is an anonymous function! return Math.pow(x, y); };}powerOfTwo = powersOf(2);console.log(powerOfTwo(1)); // 2console.log(powerOfTwo(2)); // 4console.log(powerOfTwo(3)); // 8powerOfThree = powersOf(3);console.log(powerOfThree(3)); // 9console.log(powerOfThree(10)); // 59049
The function returned here does not need to be named. It can be used anywhere outside the powersOf () function. This is an anonymous function.
Do you still remember the function of the accumulators? It can be rewritten using anonymous functions.
Var obj1 = {value: 1}, obj2 = {value: 2}, obj3 = {value: 3}; var values = (function () {// anonymous function var values = []; return function (obj) {// There is an anonymous function! If (obj) {values. push (obj. value); implements return values;} else {return values ;}}) (); // Let it execute console. log (values (obj1); // Returns: [obj. value] console. log (values (obj2); // Returns: [obj. value, obj2.value] obj1 = {value: 1} obj2 = {value: 2} obj3 = {value: 3} values = do-> valueList = [] (obj) -> valueList. push obj. value if obj valueListconsole. log (values (obj1); # Returns: [obj. value] console. log (values (obj2); # Returns: [obj. value, obj2.value]
Awesome! A high-order anonymous pure function. Why are we so lucky? In fact, there is also a self-executed structure (function (){...})();. The parentheses following the function allow the function to be executed immediately. In the above example, the value assigned to the values outside is the result of function execution.
Anonymous functions are not just syntactic sugar, but also the embodiment of lambda calculus. Please let me go ...... Lambda calculus came into being long ago when computers and computer languages were invented. It is just a mathematical concept for studying functions. It is unusual that although it only defines three expressions: variable reference, function call, and anonymous function, it is found to be completely Turing. Today, lambda calculus is at the core of all functional languages, including javascript.
For this reason, anonymous functions are often called lambda expressions.
Anonymous functions also have a disadvantage, that is, they are difficult to identify in the call stack, which may cause some difficulties for debugging. Be careful when using anonymous functions.
Method chain
In Javascript, it is common to link methods together. If you have used jQuery, you should have used this technique. It is also called the builder mode ".
This technology simplifies the code that multiple functions are applied to one object in turn.
// Each function occupies one row for calling ...... Arr = [1, 2, 3, 4]; arr1 = arr. reverse (); arr2 = arr1.concat ([5, 6]); arr3 = arr2.map (Math. sqrt );//...... Put them together in a row. console. log ([1, 2, 3, 4]. reverse (). concat ([5, 6]). map (Math. sqrt); // The brackets may indicate what is going on in the console. log ([1, 2, 3, 4]). reverse ()). concat ([5, 6]). map (Math. sqrt ));
This is only valid when the function is the method of the target object. If you want to create your own function, for example, to zip the two arrays together, you must declare it as a member of the Array. prototype object. See the code snippet below the example:
Array.prototype.zip = function (arr2 ){
//...
}
In this way, we can write it as follows:
Arr.zip ([11,12, 13,14). map (function (n) {return n * 2 });
// Output: 2, 22, 4, 24, 6, 26, 8, 28
Recursion
Recursion should be the most famous functional programming technology. A function calls itself.
Sometimes strange things happen when the function calls itself. It is a loop. It executes the same code multiple times and is also a function stack.
When using recursive functions, you must be very careful to avoid infinite loops (Here it should be called infinite recursion ). Like a loop, there must be a stop condition. This is called a base case ).
The following is an example.
Var foo = function (n) {if (n <0) {// return 'hello ';} else {// return foo (n-1);} console. log (foo (5 ));
Note: the code in the original article is incorrect. The return is missing for function calls in recursive situations, resulting in no final result for function execution. It has been corrected here.
Recursion and loops can be converted to each other. However, recursive algorithms are often more suitable and even necessary, because in some cases it is difficult to use loops.
An obvious example is traversing the tree.
var getLeafs = function(node) { if (node.childNodes.length == 0) { // base case return node.innerText; } else { // recursive case: return node.childNodes.map(getLeafs); }}
Divide and conquer
Recursion is not just an interesting way to replace the for and while loops. There is a divide-and-conquer algorithm that recursively splits the problem into smaller cases until it can be solved.
In history, there was a euclidean algorithm used to find the maximum denominator of two numbers.
Function gcd (a, B) {if (B = 0) {// baseline () return a;} else {// recursion (points) return gcd (B, a % B); Activities} console. log (gcd (12, 8); console. log (gcd (100,20); gcb = (a, B)-> if B is 0 then a else gcb (B, a % B)
In theory, divide and conquer is awesome, but is it useful in reality? Of course! Using Javascript Functions to sort arrays is not very good. It not only replaces the original array, that is, the data is not unchanged, and it is not reliable and flexible. By divide and conquer, we can do better.
There are about 40 lines of implementation code. Here, only pseudo code is displayed:
Var mergeSort = function (arr) {if (arr. length <2) {// baseline: The returned items;} else {// recursion is not needed for arrays with only 0 or 1 element: splits, sorts, and merges arrays. var middle = Math. floor (arr. length/2); // minute var left = mergeSort (arr. slice (0, middle); var right = mergeSort (arr. slice (middle); // mer// merge is an auxiliary function that returns a new array. It combines the two arrays and returns merge (left, right );}}
Note: a better example of sorting with a divide-and-conquer approach is fast sorting, and there are only 13 lines of code using Javascript. For more information, see my previous blog titled elegant functional programming language.
Evaluate inertia
The evaluate of inertia is also called a non-strict evaluate. It calls and delays execution as needed. It is an evaluate policy that calculates the function result only when necessary, this is particularly useful for functional programming. For example, if the row of code is x = func (), the return value obtained by calling this func () function is assigned to x. However, x is not important at the beginning until x is used. The call of func () is the value of inertia only when x is required.
This policy can significantly improve performance, especially when using method chains and arrays, the most popular program stream technology for functional programmers. One of the exciting advantages of inertia evaluation is that it makes infinite sequences possible. Because it does not need to be calculated before it can continue to delay. It can look like this:
// Idealized JavaScript pseudocode: var infinateNums = range (1 to infinity); var tenPrimes = infinateNums. getPrimeNumbers (). first (10 );
This opens the door to many possibilities, such as Asynchronous execution, parallel computing, and combination.
However, there is another problem. Javascript itself does not support inertia evaluation. That is to say, there is a function library that allows Javascript to simulate inertia evaluation. This is the topic of Chapter 3.