Journal entry

A long time ago, the group shared a story about functional programming, and after hearing it, it felt like it didn’t make a difference. There are many concepts in functional programming that are difficult to digest for those familiar with and accustomed to object-oriented programming. Furthermore, getting started with JavaScript functional programming is especially difficult for people who can’t even distinguish values from concepts like references and closures. “Functional-light JavaScript” (Chinese translation version of “JavaScript Lightweight Functional Programming”) claims to be lightweight, leaving aside the explanation of concepts, from why to use, how to use, the final presentation of several aspects to bring us a new understanding of the basic and important concept of function.

Why functional programming

There are three programming paradigms, structured programming, object-oriented programming and functional programming. Although functional programming is a relatively recent programming paradigm, it is the earliest to be invented. The concept of functional programming is based on the direct derivative of 𝛌 calculus, and a core idea of 𝛌 algorithm is invariance. Therefore, in theory, assignment statements should not exist in functional programming languages.

The principles of functional programming allow us to write code that is readable and trustworthy, but that does not mean it is easy to understand. The declarative style lets us know what programs do, but writing such programs is not easy to understand, at least for those new to functional programming.

Most of the time you spend maintaining your code is spent reading and understanding it. Sometimes we need to carefully read every line of code in case an unknown operation causes a series of bugs. Sometimes you also need to understand the intent of the program in different function calls. In summary, there are some principles of functional programming that allow us to write readable, verifiable code. Of course, just because you’ve mastered functional programming doesn’t mean you have to use it.

The best code is readable code because it strikes the right balance between what is right (idealistic) and what is necessary (correct).

Basis function

In mathematics, a function always takes some input value and returns an output value that has some kind of mapping between the input value and the output value. In functional programming, functions are used in the mathematical sense, taking input values and specifying return values.

Functions usually look like this:

function foo(x, y, z){
    / /...
    }
foo.length  / / 3
foo(1.2.3);
Copy the code

Foo. length is the number of parameters the function expects, usually the number of parameters. However, arguments are not required to have the same number of arguments. Inside a function you can view all input arguments using arguments (arrow functions don’t have this argument).

Using deconstruction in ES6, you can easily bind variables to the values of the parameters:

function foo([x, y, ...args]) {
    / /...
    }

function foo(params) {
    var x = params[0];
    var y = params[1];
    var args = params.slice(2);
    / /...
}
Copy the code

From the above comparison, we can see that declarative code is generally cleaner than imperative code. So use declarative and self-explanatory code whenever possible.

Sometimes functions overload different functions based on different input values:

function foo(x,y) {
    if (typeof x == "number" && typeof y == "number") {
        return x * y;
    }
    else {
        returnx + y; }}Copy the code

Although this design is convenient for the time being, it will cause a lot of trouble to add or modify the function in the future.

As mentioned above, a function must have an explicit return value. If a function changes the input parameter of a variable or reference type in the outer scope, then the function has the side effect of polluting the environment outside the function. We want the function to be a function with no side effects, a pure function.

function sum(list) {
    var total = 0;
    for (let i = 0; i < list.length; i++) {
        if(! list[i]) list[i] =0;

        total = total + list[i];
    }

    return total;
}

var nums = [ 1.3.9.27.84 ];

sum( nums );            / / 124
Copy the code

A function can take and return a value of any type. When the value of that type happens to be a function, it is called a higher-order function. The really advanced use, however, is closures, which can record and access variables outside the scope, even if the function is executed in a different scope.

function runningCounter(start) {
    var val = start;

    return function current(increment = 1){
        val = val + increment;
        return val;
    };
}

var score = runningCounter( 0 );

score();                / / 1
score();                / / 2
score( 13 );            / / 15
Copy the code

Variables stored in closures are not automatically destroyed after the function is executed; they are retained as long as the internal function still has a reference to them. Closures are the most important foundation of functional programming, so it’s important to understand this concept.

There are also functions that have no names. These are called anonymous functions, and the most commonly used are immediate expressions:

(function(){

    / / I am IIFE!}) ();Copy the code

Named functions have many advantages, such as easy debugging in the stack, being able to reference themselves by name, and so on. But not all functions can be named, arrow functions can be said to be very concise, you can use the simplest notation to represent a function.

people.map( person= > person.nicknames[0] || person.firstName );
Copy the code

In addition to the simplicity of the arrow function, there is another benefit that can be called this. The this in the arrow function is scope-based and predictable. For normal functions, this is dynamically bound and caller-based. Either way, we still want to be able to express the context explicitly.

function sum(ctx) {
    return ctx.x + ctx.y;
}

var context = {
    x: 1.y: 2
};

sum( context );
Copy the code

Input to function

In the program tends to encapsulate the immutable and isolate the changeable. Most of the time invariant includes some preset parameters.

First wrap a request function:

// Step 1:
function ajax(url,data,callback) {
    // ..
}
Copy the code

For different requests, there are different urls or parameters:

// Step 2:
function getPerson(data,cb) {
    ajax( "http://some.api/person", data, cb );
}

function getOrder(data,cb) {
    ajax( "http://some.api/order", data, cb );
}
Copy the code

The URL is the default parameter, and the rest of the parameters are passed in at call time.

// Step 3:
getPerson( { user: CURRENT_USER_ID }, cb );
Copy the code

On closer inspection of step 2, it seems that the code can also be refined:

// Step 2:
var partial = (fn, ... presetArgs) = >(... laterArgs) => fn(... presetArgs, ... laterArgs);var getPerson = partial(ajax, "http://some.api/person");
var getOrder = partial(ajax, "http://some.api/order");
Copy the code

The conversion of step 2 is the application of partial functions, which reduce the number of input parameters by presetting parameters. If you reverse the order of the input parameters, it becomes a right partial function.

Also a technique for managing the input of functions, called currization, this technique breaks a function that takes multiple arguments into a continuous chain function, each chain function taking a single argument and returning the function that takes the next argument.

function sum(. args) {
    var sum = 0;
    for (let i = 0; i < args.length; i++) {
        sum += args[i];
    }
    return sum;
}

sum(1.2.3.4.5 ); 

/ / curry

var curry =
    (fn, arity = fn.length, nextCurried) = >
        (nextCurried = prevArgs= >
            nextArg => {
                var args = prevArgs.concat( [nextArg] );
                if (args.length >= arity) {
                    returnfn( ... args ); }else {
                    returnnextCurried( args ); }})([]);// (5 is used to specify the number of chained calls)
var curriedSum = curry( sum, 5 );
curriedSum( 1) (2) (3) (4) (5 );
Copy the code

As can be seen from the above code, both partial functions and Currization are executed after all arguments are collected, that is, there is no need to pass in all arguments at once, and currization, which only accepts a single argument, also plays an important role in subsequent combinative functions.

Functional programming puts a lot of effort into parameters, and even has a style called invisible parameters, whose purpose is to remove mappings between all but essential parameters and their arguments. It’s up to you to decide whether the invisible parameter style improves the readability of your code, and whether it’s necessary to use it.

function double(x) {
    return x * 2;
}

[1.2.3.4.5].map( function mapper(v){
    returndouble( v ); });// Invisible parameter style;
[1.2.3.4.5].map( double );
Copy the code

Combination function

Function combination is to take the output value of the previous function as the input value of the next function, and so on.

Here’s an example of splitting a sentence into an array of words:

// Put the word in lower case;
function words(str) {
    return String( str )
        .toLowerCase()
        .split( /\s|\b/ )
        .filter( function alpha(v){
            return /^[\w]+$/.test( v ); }); }/ / to heavy;
function unique(list) {
    var uniqList = [];
    for (let i = 0; i < list.length; i++) {
        // value not yet in the new list?
        if (uniqList.indexOf( list[i] ) === - 1) { uniqList.push( list[i] ); }}return uniqList;
}


var text = "To compose two functions together, pass the \
output of the first function call as the input of the \
second function call.";

function uniqueWords(str) {
    return unique( words( str ) );
}

var wordsUsed = uniqueWords(text);
// ["to","compose","two","functions","together","pass",
// "the","output","of","first","function","call","as",
// "input","second"]
Copy the code

Data flow:

wordsUsed <-- unique <-- words <-- text
Copy the code

But functional programming likes a declarative style, where the focus is on what, not how, and we don’t need to know the implementation details. We hope so:

/ / imperative
function shorterWords(text) {
    return skipLongWords( unique( words( text ) ) );
}

/ / the declarative
var shorterWords = compose( skipLongWords, unique, words );
Copy the code

How to implement the function compose?

//
var compose =
    (. fns) = >
        result => {
            var list = fns.slice();

            while (list.length > 0) {
                // Take the last function from the end of the list
                // And execute it
                result = list.pop()( result );
            }

            return result;
        };

//
var compose =
    (. fns) = >
        fns.reverse().reduce( (fn1,fn2) = >(... args) => fn2( fn1( ... args ) ) );//
var compose =
    (. fns) = > {
        // Take out the last two arguments
        var [ fn1, fn2, ...rest ] = fns.reverse();

        var composedFn =
            (. args) = >fn2( fn1( ... args ) );if (rest.length == 0) return composedFn;

        returncompose( ... rest.reverse(), composedFn ); };Copy the code

Immutability of side effects and values

Const is used to declare a constant, which is a variable that cannot be reassigned. In troubleshooting, we rarely pay attention to a variable declared by const because it is, after all, an insignificant constant. Wouldn’t it be exciting if this constant changed in the end? ! Although const declares a constant, the program allows it to change for values of reference types. Because const is unreliable, we have to refocus on constants declared by it.

const x = [ 2 ];
x[0] = 3;
Copy the code

Data mutations have led to a number of unexpected bugs, caused by variables being shared or used differently.

var x = 1;

foo();

console.log( x );

bar();

console.log( x );

baz();

console.log( x );
Copy the code

When variables are shared (in functions foo(), bar(), baz(), any one of them might change variable x), we end up not knowing what value x is, and we have to read functions foo(), bar(), baz() one by one in order to track the change in variable x. Obviously, this makes it difficult to read and understand the code.

function sum(list) {
    var total = 0;
    for (let i = 0; i < list.length; i++) {
        if(! list[i]) list[i] =0;

        total = total + list[i];
    }

    return total;
}

var nums = [ 1.3.9.27.84 ];

sum( nums );            / / 124

sums;    //[1, 3, 9, 27, 0, 84]
Copy the code

For different variables that reference the same object, the object changes synchronously, which is often imperceptible. Therefore, special attention should be paid to the particularity of reference types.

Generalize what variables need extra care to be shared between variables in the outer scope and variables of reference type.

How to avoid the state disorder caused by this data mutation? You can think of making the variable read-only or copying it again for differentiation.

var x = Object.freeze( [ 2.3[4.5]]);// Change is not allowed:
x[0] = 42;

// oops, still allow changes:
x[2] [0] = 42;
Copy the code

You can use an API like Object.freeze to make the data immutable, but this API is limited to a shallow limit on immutable. Making a deeply nested Object immutable requires deep traversal using apis such as Object.freeze for each attribute.

Another idea is to make a copy of the data, so that no amount of messing with the copy will affect the source data.

function addValue(arr) {
    var newArr = [ ...arr, 4 ];
    return newArr;
}

addValue( [1.2.3]);/ / [1, 2, 3, 4]
Copy the code

This reminds us of the immutability that is at the heart of functional programming. We saw the bad effects of changing data on the readability and state of the program, and naturally we thought that data invariance would make the program more user-friendly. But the immutability of a value does not mean that the state represented by the value will not change, so there is a copy of the value. This immutability means that when we need to change a state in the program, we cannot change the source data, but make a copy that tracks it. There are deep and shallow copies, and what we need is a copy that is independent of the source data.

function updateLastLogin(user) {
    var newUserRecord = Object.assign( {}, user );
    newUserRecord.lastLogin = Date.now();
    return newUserRecord;
}

var user = {
    // ..
};

user = updateLastLogin( user );
Copy the code

However, copying is not without costs. All of a sudden, extra copies can affect performance somewhat. We wanted a way to reduce memory footprint and improve performance. It allows different variables to point to the same data source and have different versions of the data without affecting each other. This is a bit like Git’s version control, in which case the semantics of value invariance are preserved. There are already several libraries that implement similar optimizations, such as Immutable.

If you can sum up the above in two words, it is undoubtedly “interference”, which comes from unexpected changes in data.

As the protagonist of functional programming — function, we naturally hope that the function will not cause data mutation, all the results are predictable, and more hope that it can meet the core of functional programming — invariance. In other words, we need to reduce the side effects of the function in the process of coding.

In addition to the data sharing mentioned above, side effects include randomness (math.random ()), IO operations, race problems, and more. Side effects are varied, and we use pure functions to reduce them. In other words, anything that goes against a pure function is basically a side effect.

What is a pure function? It is said that pure functions are idempotent functions, but not all pure functions are idempotent in mathematical concepts.

var hist = document.getElementById( "orderHistory" );

// idempotent:
hist.innerHTML = order.historyText;

// non-idempotent:
var update = document.createTextNode( order.latestUpdate );
hist.appendChild( update );

// non-idempotent:
function calculateAverage(list) {
    var sum = 0;
    for (let i = 0; i < list.length; i++) {
        sum += list[i];
    }
    return sum / list.length;
}

calculateAverage( [1.2.4.7.11.16.22]);/ / 9
Copy the code

Another way to define a function is that, given the same input (one or more), it always produces the same output.

const PI = 3.141592;

function circleArea(radius) {
    return PI * radius * radius;
}
Copy the code

Another definition is that pure functions have referential transparency.

Referential transparency means that a function call can be replaced by its output value without changing the behavior of the entire program. In other words, it is impossible to tell from the execution of the program whether the function call was executed or whether its return value is inline at the location of the function call.

function calculateAverage(list) {
    var sum = 0;
    for (let i = 0; i < list.length; i++) {
        sum += list[i];
    }
    return sum / list.length;
}

var nums = [1.2.4.7.11.16.22];

var avg = calculateAverage( nums );

console.log( "The average is:", avg );        // The average is: 9

var avg = 9;

console.log( "The average is:", avg );        // The average is: 9
Copy the code

The side effects of the code reduce the quality of the program and make it harder to read. However, there is no such thing as a program without side effects, we simply avoid them by refactoring impure functions or encapsulating side effects.

Closures and objects

Let’s start with two pieces of code describing the same thing:

// Closure
function person(name,age) {
    return happyBirthday(){
        age++;
        console.log(
            "Happy " + age + "th Birthday, " + name + "!"); }}var birthdayBoy = person( "Kyle".36 );
birthdayBoy();            // Happy 37th Birthday, Kyle!

// Object mode
var birthdayBoy = {
    name: "Kyle".age: 36,
    happyBirthday() {
        this.age++;
        console.log(
            "Happy " + this.age + "th Birthday, " + this.name + "!"); }}; birthdayBoy.happyBirthday();// Happy 37th Birthday, Kyle!
Copy the code

You can see that closures and objects are two ways of expressing the same thing.

Closures and objects have the following arguments:

  1. A programming language without closures can simulate closures with objects.
  2. A programming language without objects can simulate objects with representations.

Now, we simulate closures with objects in JavaScript.

function outer() {
    var x = 1;

    return function inner(){
        return x;
    };
}
Copy the code

The first thing to resolve is that the x variable is not destroyed after outer() is executed. When we create function outer, we have an object that holds all variables in the scope:

scopeOfOuter = {
    x: 1
};
Copy the code

When we create inner, we also generate a scopeOfInner object whose prototype points to scopeOfOuter:

scopeOfInner = {};
Object.setPrototypeOf( scopeOfInner, scopeOfOuter );
Copy the code

So when accessing the x variable, it’s more like accessing scopeoFinner.x and looking up the prototype chain.

These are just assumptions. The reality is more complicated than that. This is just an argument that we can implement closures using objects, and we can also implement objects using closures.

recursive

Recursion can be said to be very functional, it has the advantage of more declarative lines, concise and easy to read, but the biggest drawback is the memory aspect.

function foo() {
    var z = "foo!";
}

function bar() {
    var y = "bar!";
    foo();
}

function baz() {
    var x = "baz!";
    bar();
}

baz();
Copy the code

Each function call opens up a small area of memory called a stack frame. The stack frame contains some important information about the current state of the function statement, including the value of any variable. This happens because one function pauses to execute another function, and when the other function is finished, the engine needs to return to the state where it was suspended.

For recursion, however, tens of thousands of stacks may be generated, eventually causing the stack to burst.

We want the stack of outer functions to pop up when the inner function is called at the bottom of the function.

This technique is called tail-call optimization.

Other ways to reduce memory include the Successor Transfer style (CPS) and Trampoline.

CPS transformation is used to rewrite any recursive function into the form of tail call, in the form of continuation chain, transfer the stack space occupied by recursion to the heap, to avoid the tragedy of stack burst. (CPS transforms can be used to transform ordinary recursion into tail recursion.)

In languages without tail-call optimization, state and parameter compaction occurs without knowing that the function will not return, so you need to manually force out the function called at the next level to prevent the interpreter from compaction. This is called a Trampoline.

// Fibonacci
function fib(n) {
    if (n <= 1) return n;
    return fib( n - 2 ) + fib( n - 1 );
}

// CPS
function fib(n,cont = r => r) {
    if (n <= 1) return cont( n );
    return fib(
        n - 2,
        n2 => fib(
            n - 1,
            n1 => cont( n2 + n1 )
        )
    );
}

function trampoline(fn) {
    return function trampolined(. args) {
        varresult = fn( ... args );while (typeof result == "function") {
            result = result();
        }

        return result;
    };
}

/ / the trampoline
trampoline(fib(n,cont = r= > r) {
    if (n <= 1) return cont( n );
    return (a)= > fib(
        n - 2,
        n2 => fib(
            n - 1,
            n1 => cont( n2 + n1 )
        )
    );
})
Copy the code

At the end of the article

There are other chapters in the book, most of which are practical. For JavaScript functional programming, this book is a good choice.

My blog