This article is not about what functional programming is or why it should be used, but rather about the best practices of functional programming. Hopefully, after reading this article, you will understand and love functional programming.

1. Pure functions

A pure function is a function that always produces the same output for the same input, with no side effects. Try to use pure functions in your code. This will make your code more robust and easier to test.

Let’s take a look at the side effects

1.1 Reference free variables

Look at the code below

  function foo(x) {
    y = x * 2;
  }
  var y;
  foo( 3 );
Copy the code

Analyzing the code above, calling the function changes the variable y outside the function, causing side effects and therefore not pure functions

When a function refers to a variable outside the function, that is, a free variable, not all free variable references are bad, but we have to be very careful when dealing with them

We can turn it into a pure function very easily

function foo(x) {
    return x*2;
}
foo( 3 );
Copy the code

1.2 random number

Random numbers can have side effects, too. For the same input, the result is unpredictable

1.3 the IO

The most common side effect is input/output. It makes absolutely no sense for a program to have no IO because its work cannot be observed in any way, to take the most common example

var users = {};

function fetchUserData(userId) {
    ajax( `http://some.api/user/${userId}`.function onUserData(user){ users[userId] = user; }); }Copy the code

FetchUserData changes users, and to be a bit purer, we can create a wrapper function called safer_fetchUserData that wraps both external variables and impure functions

function safer_fetchUserData(userId,users) {
    // Make a copy of the external variable
    users = Object.assign( {}, users );

    fetchUserData( userId );

    return users;


    / / * * * * * * * * * * * * * * * * * * * * * * *

    // Original impure function:
    function fetchUserData(userId) {
        ajax(
            `http://some.api/user/${userId}`.function onUserData(user){ users[userId] = user; }); }}Copy the code

Safer_fetchUserData is a little purer, but it’s still not a pure function, because the side effects of Ajax depend on the result of the Ajax call. As you can see from the above, the side effects are not completely eliminated, so we can only write as pure a function as possible, gathering all the impure parts together

2. Unary function (single parameter)

Consider the following code

["1"."2"."3"].map( parseInt );
Copy the code

Many people would answer [1,2,3] without thinking, but the real result is [1, NaN, NaN]. Consider array.map (fn), a higher-order function, for each iteration, the fn function is executed. It takes three arguments, the element of the round of the array, the index, and the array itself, so the actual execution is

parseInt("1", 0, ["1"."2"."3"])
parseInt("2", 1,"1"."2"."3"])
parseInt("3", 2,"1"."2"."3"])
Copy the code

ParseInt (x,radix) can accept two parameters, because the third parameter is omitted, the first parameter x represents the value to be converted, and the second parameter RADIX is the conversion base. When the parameter radix is set to 0, or the radix is not set, ParseInt () determines the base of a number based on string.

When ignoring parameter radix, the cardinality of JavaScript default numbers is as follows:

If a string begins with “0x”, parseInt() parses the rest of the string into a hexadecimal integer.

If a string begins with 0, ECMAScript V3 allows an implementation of parseInt() to parse the following character into an octal or hexadecimal number.

If a string begins with a number from 1 to 9, parseInt() will parse it as a decimal integer. So it is not surprising that the final result is [1, NaN, NaN]

So how do you solve this problem and return the desired result, which is something that most people think of without thinking

["1"."2"."3"].map(v= > parseInt(v));
Copy the code

Yes, you can get the right answer, very simple, very straightforward, but isn’t there a way to encapsulate it a little bit and extend it a little bit in functional programming, where you can wrap this unary transformation function around the target function, making sure that the target function only takes one argument

function unary(fn) {
    return function onlyOneArg(arg){
        return fn(arg);
    };
}
["1"."2"."3"].map( unary(parseInt));/ / [1, 2, 3]
Copy the code

Partial functions

Fixed one or more arguments to a function, returns a new function that accepts the remaining arguments, used more often with map

    function partial(fn, ... presetArgs) {
        return function partiallyApplied(. laterArgs) {
           returnfn(... presetArgs, ... laterArgs ) } }1 / / application
    var getPerson = partial( ajax, "http://some.api/person" )
    var getOrder = partial( ajax, "http://some.api/order" )
    // version1
    var getCurrentUser = partial(ajax, "http://some.api/person", {user: "hello world"})
    // version 2
    var getCurrentUser = partial( getPerson, { user: CURRENT_USER_ID } );
    2 / / application
    function add(x, y){
        return x + y
    }
    [1.2.3.4.5].map( partial( add, 3))Copy the code
    function partialRight(fn, ... presetArgs) {
        return function partiallyApplied(. laterArgs) {
            returnfn(... laterArgs, ... presetArgs) } }Copy the code

4. Curry

Currization is a technique for converting a function that takes multiple arguments into a series of functions that take one argument.

function add(a, b) { return a + b; // add(1, 2); // add(1, 2); addCurry(1)(2) // 3Copy the code

Here is a simple implementation

function sub_curry(fn) {
    var args = [].slice.call(arguments.1);
    return function() {
        return fn.apply(this, args.concat([].slice.call(arguments)));
    };
}

function curry(fn, length) {
    length = length || fn.length;
    var slice = Array.prototype.slice;
    return function() {
        if (arguments.length < length) {
            var combined = [fn].concat(slice.call(arguments));
            return curry(sub_curry.apply(this, combined), length - arguments.length);
        } else {
            return fn.apply(this.arguments); }}; }Copy the code

5. The combination compose

Function combination, the output of one function as the input of another function, so that the data flow can be like water flowing in a pipe, in order to combine, must ensure that the combination of function parameters can only have one, and must have a return valueCopy the code
    // Execute from right to left
    function compose(. fn) {
        return function composed(result){
            var list = [...fn]
            while(list.length > 0) {
                result = list.pop()(result)
            }
            return result
        }
    }
    // The pipe function moves from left to right
    function pipe(. fn) {
        return function piped(result) {
            var list = [...fn]
            while(list.length > 0) {
                result = list.shift()(result)
            }
            return result
        }
    }

Copy the code

6. The recursive (recursion)

// Determine whether a number is prime
function isPrime(num,divisor = 2){
    if (num < 2 || (num > 2 && num % divisor == 0)) {
        return false;
    }
    if (divisor <= Math.sqrt( num )) {
        return isPrime( num, divisor + 1 );
    }
    return true;
}
// Calculate the depth of the binary tree
function depth(node) {
    if(node) {
            let depthLeft = depth(node.left)
            let depthRight = depth(node.right)
            return 1 + Math.max(depthLeft, depthLeft)
    }
    return 0
}
Copy the code
  • If the recursion is too deep, there will be memory overflow problems and need to be optimized with tail calls
    // Solve stack overflow problem, tail call optimization
    // The concept of tail-call is very simple, which means that the last step of a function is to call another function.
    // None of the following
    / / a
    function f(x){
      let y = g(x);
      return y;
    }
    
    / / 2
    function f(x){
      return g(x) + 1;
    }
    
    // The factorial function
    function factorial(n) {
        if( n === 1) {
            return 1
        }
        return n*factorial(n- 1)}Copy the code

Change the factorial function to a tail call, ensuring that the last step is to call only itself, which is to rewrite all internal variables used as arguments to the function

    function factorial(n, total) {
        if (n===1) {
          return total  
        }
        return factorial(n - 1, n*total)
    }
    // But this will pass two arguments, using two functions to rewrite
    function factorial(n) {
        return tailFactorial(n ,1)}function tailFactorial(n, total) {
        if (n===1) {
          return total  
        }
        return tailFactorial(n - 1, n*total)
    }
    
    // Continue rewriting, tailFactorial goes inside factorial
    function factorial(n) {
        function tailFactorial(n, total) {
            if (n===1) {
            return total  
            }
            return tailFactorial(n - 1, n*total)
        }
        return tailFactorial(n ,1)}// You can also use the Curry function to convert a multi-argument function to a single-argument form
    function currying(fn, n) {
      return function (m) {
        return fn(m, n);
      };
    }
    function tailFactorial(n, total) {
        if (n===1) {
          return total  
        }
        return tailFactorial(n - 1, n*total)
    } 
    var factorial = currying(tailFactorial, 1)
    factorial(5)
Copy the code