The way of thinking about functional programming is to abstract things and their relationships from the real world into the world of programming (by abstracting the processes of computation) Sum up the lecture on functions in Volume 1.)

This chapter focuses on high-level functions in Javascript and functional programming.

Why learn functional programming?

  • Vue/React began to embrace functional programming
  • Functional programming can discard this
  • Better use of Tree Shaking to filter out useless code during packaging
  • Convenient for testing and parallel processing
  • There are many libraries to help with functional development :loadsh, underscore, ramda

What is functional programming

Functional Programming (FP):FP is one of the Programming paradigms.

The way of thinking of object-oriented programming: abstract the things in the real world into classes and objects in the program world, and demonstrate the connection of things and events through encapsulation, inheritance and polymorphism

The way of thinking about functional programming is to abstract things and their relationships from the real world to the program world (to abstract the process of computation).

  • The essence of the program: according to the input through some operation to obtain the corresponding output, the program development process will involve many functions with input and output
  • X ->f ->y,y=f(x)
  • A function in functional programming is not a function (method) in a program, but a function (mapping) in mathematics, for example:y=sin(x)The relationship between x and y
  • The same input always produces the same output
  • Functional programming is used to describe mappings between data (functions)
function test(x){
	return x * x;
}
Copy the code

Functions are first-class citizens in Javascript; they can be stored in variables, functions as arguments, and functions as return values.

Higher-order functions in JavaScript

Higher-order functions

Function as a parameter, the following code implements a loop through the array, passing the parameter callback function to get the value of each array to be processed in the callback function

/ / simulation forEach
function forEach(array, fn) {
    for (let index = 0; index < array.length; index++) {
        constelement = array[index]; fn(element); }}Copy the code

The following functions can be used as return values. Generally speaking, functions as return values are representations of closures. We will learn more about closures later

function test(x){
	return function(y){
				returnx + y; }}let a = test(1) (2);/ / 3
Copy the code

Meaning of higher order functions

  • Abstraction helps us mask the details and focus only on our goals
  • Higher-order functions are used to abstract general problems

Contrast procedural approach with functional programming approach

Commonly used high-order functions, the following to simulate JavaScript’s own high-order functions, the following code commonly used high-order functions use a large number of functions as parameters, callback. Just get the results and process them.

  • ForEach – function as argument
/ / simulation forEach
function forEach(array, fn) {
    for (let index = 0; index < array.length; index++) {
        constelement = array[index]; fn(element); }}Copy the code
  • Filter – functions as arguments
/ / analog filter
function filter(array, fn) {
    let result = [];
    for (let index = 0; index < array.length; index++) {
        const element = array[index];
        if(fn(element)) { result.push(element); }}return result;
}
Copy the code
  • Every – function as argument
// The match condition returns true only when all elements of the every array perform some kind of operation and all elements are true otherwise false is returned when one element is not true
const every = (arr, fn) = > {
    let result = false;
    for (const iterator of arr) {
        result = fn(iterator);
        // If one of these returns false, it is not true
        if(! result) {break; }}return result;
}
Copy the code
  • Some – functions as arguments
// Some returns true if only one element matches, false only if all elements do not match
const some = (arr, fn) = > {
    let result = false;
    for (const value of arr) {
        result = fn(value);
        if (result) {
            break; }}return result;
}
Copy the code
  • Once – Function as argument
// The simulated once function can only be executed once
function once(fn) {
    let done = false;
    return function () {
        if(! done) { done =true;
            return fn.apply(this.arguments);// The argument passed by function() is passed to fn}}}let pay = once((money) = > {
    console.log(` paid${money} RMB`);
});
Copy the code
  • Map – functions as arguments
// The map function uses const for iterating through each element of an array and changing the value of each element without expecting the function to be defined as a constant
const map = (array, fn) = > {
    let results = [];
    for (const value of array) {
        results.push(fn(value));// returns the result of fn's processing
    }
    return results;
}
Copy the code

closure

Closures: A function is bundled together with references to its surrounding state (lexical environment) to form a closure.

  • Closures can call an inner function of a function in another scope and access members of that function’s scope

As with the once function above, the new function returned can still call the once() internal variable done

function once(fn) {
    let done = false;
    return function () {
        if(! done) { done =true;
            return fn.apply(this.arguments);// The argument passed by function() is passed to fn}}}Copy the code
  • The essence of closures: functions are placed on an execution stack at execution time and removed from the stack when the function completes execution, but scoped members on the heap cannot be freed because of external references, so internal functions can still access the members of the external function.

An in-depth understanding of closures


      
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <title>Document</title>
</head>
<body>
    <script>
        /* Closure case */
        Math.pow(4.2);//4 to the second 5 to the second
        // Use a function to simplify squaring
        function makePow(power){
            // Return a function to the power of the number passed
            return function(value){
                return Math.pow(value,power); }}/ / square
        let power2 = makePow(2);
        // Take the third power
        let power3 = makePow(3);

        console.log(power2(2));
        console.log(power2(4));
        console.log(power3(4));

    </script>
</body>
</html>
Copy the code

Let’s take a look at the closure process by debugging the above code

In the following figure, there are two places to focus on, one is to set the pilot and refresh the page to see the debugging tool on the right, focus on the Call Stack and Scope to see that the current Scope is in the Global Global Scope.

Press F11 or Command +; Perform the following results at this time next makePow function, can see the top of the Call Stack Call Stack for makePow, while the Scope Scope is a Local storage power and Local Scope this: Window Through debugging we can see a lot of useful information to help us understand the program.

Then we let the program execute to the log step. If we look at the following view, we can see that the Scope has a Script Scope that stores the value of the let variable. Let has a separate Scope Script.

The next step is to see how the stack executes the power2() anonymous function. Where does power come from in this anonymous function? Closure(makePow) is a Closure that references the power of makePow :2. As mentioned above, external functions are removed from the call stack when a closure occurs, but variables associated with the closure are cached, in this case power.

In the case of executing POWER3, cache power:3 as well. So that’s the whole process of closure. It is much easier to understand the concept and implementation of closures through debugging than it is to understand the theoretical stuff, so it is more about mastering the method.

Pure functions

Pure functions: The same input always yields the same output without any observable side effects

  • A pure function is like a mathematical function (describing the relationship between input and output),y=f(x);
  • Lodash is a library of pure functions that provides methods for operating on arrays, numbers, objects, strings, functions, and more.
  • Arrays of slice and splice are pure and impure functions, respectively
    • Slice returns the specified portion of the array without altering the original array
    • Splice operates on an array and returns that array, changing the original array
let array = [1.2.3.4.5];
console.log(array.slice(0.3));
console.log(array.slice(0.3));
console.log(array.slice(0.3));
// The same input and the same output is a pure function
//[1, 2, 3]
// [1, 2, 3]
// [1, 2, 3]

//splice is not a pure function because the input is the same but the output is different each time
console.log(array.splice(0.3));
console.log(array.splice(0.3));
console.log(array.splice(0.3));
//splice is an impure function if the output of the same input is different each time
//[1, 2, 3]
//[ 4, 5 ]
/ / []

// Write a pure function
function getSum(n1,n2){
    return n1 + n2;
}
console.log(getSum(1.2));
console.log(getSum(1.2));
console.log(getSum(1.2));
/ / 3
/ / 3
/ / 3
Copy the code
  • Functional programming does not preserve the outcome of the computation so variables are immutable (stateless)
  • We can hand off the results of one function to another

Lodash represents pure functions

To use the LoDash library, we need to introduce the LoDash library in the nodeJS environment

//first last toUpper reverse each includes find findIndex
const_ =require('lodash');
const array = ['jake'.'tom'.'lucy'.'kate'];

console.log(_.first(array));/ / jake pure functions
console.log(_.last(array));/ / Kate pure functions
console.log(_.toUpper(_.first(array)));/ / JAKE pure functions

console.log(_.reverse(array));//[' Kate ', 'Lucy ',' Tom ', 'Jake'] //[' Kate ', 'Lucy ',' Tom ', 'Jake'

const r = _.each(array,(item,index)=>{
    console.log(item,index);
});
console.log(r);

const l = _.find(array,(item)=>{
    return item === 'jake';
});
console.log(l,array);
Copy the code

The benefits of pure functions

  • Cacheable: Because pure functions always have the same result for the same input, the results of pure functions can be cached

Lodash’s Memoize function

const _ = require('lodash');

function getArea(r) {
    console.log(r);
    // Calculate the area of the circle
    return Math.PI * r * r;
}
// Lodash's memoize method takes a pure function and the result cache of a pure function returns a function with a memory function
// let getAreaWithMemory = _.memoize(getArea);
// console.log(getAreaWithMemory(4));
// console.log(getAreaWithMemory(4));
// console.log(getAreaWithMemory(4));
50.26548245743669 50.26548245743669 50.26548245743669 50.26548245743669 */
Copy the code

Implement the memoize function manually

// Simulate the implementation of memoize method
function memoize(fn){
    let cache = {};
    return function(){
        //1 Check whether the cache has the result of this fn
        let key = JSON.stringify(arguments);// Pass the parameter as key
        cache[key] = cache[key] || fn.apply(fn,arguments);// If there is no value, call fn() as the result of the value
        returncache[key]; }}let getAreaWithMemory = memoize(getArea);
console.log(getAreaWithMemory(4));
console.log(getAreaWithMemory(4));
console.log(getAreaWithMemory(4));
/* The result is as follows: 4 50.26548245743669 50.26548245743669 50.26548245743669 */
Copy the code
  • Testable pure functions make testing easier
  • Parallel processing
    • Parallel manipulation of shared memory data in a multithreaded environment is likely to cause unexpected problems
    • Pure functions do not need access to shared memory data, so they can run at will in parallel (Web workers)

Side effects

  • Pure functions: For the same input you always get the same output without any observable side effects
// Once the value of mini changes, the function becomes impure as a side effect of external dependence
let mini = 18;
function checkAge(age){
    return age >= mini;
}

// Pure (hard coded later by Currization)
function makeCheckAge(age){
    let mini = 18;
    return age >= mini;
}
Copy the code

Side effects make a function impure. Pure functions return the same output based on the same input, and can have side effects if the function depends on external state to guarantee the same output.

Sources of side effects

  • The configuration file
  • The database
  • Get user input
  • .

All external interactions are likely to have side effects, which also make methods less generic and less suitable for extensibility and reuse; At the same time, side effects will bring security risks to the program and bring uncertainty to the program, but side effects can not be completely banned, as far as possible to control their occurrence in a controllable range.

Haskell Brooks Curry

Use currization to solve side effects of pure functions. What is Corrification? When a function has more than one argument, we modify the function to call a function that only passes and returns a new function (the arguments will never change), which receives the remaining arguments and returns the result.

  • Use Coriation to solve the hard-coded problem in the previous case
/ / hard coded
function checkAge(age){
    let min = 18;
    return age >= min;
}

// Solve the problem of hard coding plain pure functions
function checkAge(min,age){
    return age >= min;
}

console.log(checkAge(18.20));//true

// Solve the base value problem by closure
function checkAge(min) {
    return function (age) {
        returnage >= min; }}let checkAge = min= > ((age) = >(age>=min));

let checkAge18 = checkAge(18);
let checkAge20 = checkAge(20);

console.log(checkAge18(20));
console.log(checkAge18(24));
console.log(checkAge20(20));
console.log(checkAge20(24));
Copy the code
  • The Method of Currization in Lodash

Lodash’s common Cremation method

Curry (func) creates a function that accepts one or more arguments from func, if all the arguments needed by func are provided

Func is executed and returns the result of execution, otherwise continue to return the function and wait for the remaining arguments to be accepted

Parameters: functions that require currization

Return value: The currified function

const _ = require('lodash');
function getSum(a, b, c) {
    return a + b + c;
}
const curried = _.curry(getSum);

console.log(curried(1.2.3));
console.log(curried(1.2) (3));
console.log(curried(1) (2.3));
Copy the code
  • The case of Corrification
// Example: Extract whitespace from a string
const match = curry(function (reg, str) {
    return str.match(reg);
});

const haveSpace = match(/\s+/g);
const haveNumber = match(/\d+/g);

const filter = curry(function(func,arry){
    return arry.filter(func);
});

console.log(haveSpace('hello world'));
console.log(haveNumber('123abc'));

console.log(filter(haveSpace,['jonm Connm'.'Jone_Done']));

const findSpace = filter(haveSpace);// The new function looks for functions that have blank arrays in the array

console.log(findSpace(['jonm Connm'.'Jone_Done']));
Copy the code

The essence of a closure is that an internal function can access the members of an external function, while currization solves the problem of minimizing the granularity of function decomposition with multiple parameters. Notice the difference between closures and Currization they’re not the same thing.

  • The principle of Currization
// The currization principle is implemented
        function curry(func) {
            return function curriedFn(. args) {
                // Determine the number of parameters accepted anonymously and the number of func parameters
                if (args.length < func.length) {
                    // Passing only part of the argument returns a new function
                    return function () {
                        // Call curriedFn again to merge the parameters
                        returncurriedFn(... args.concat(Array.from(arguments))); }}// Call func with the same arguments
                return func(...args);
            }
        }
        function getSum(a, b, c) {
            return a + b + c;
        }
        const curried = curry(getSum);

        console.log(curried(1.2.3));
        console.log(curried(1.2) (3));
        console.log(curried(1) (2.3));
Copy the code

This is a bit of a puzzle, and it’s easy to understand with the debugging tool, as shown in the following figure: when implemented to curried(1,2)(3), you can see that there are two Closure scopes: the passed func and the value args[1,2] passed by the decompanted function

As the code continues, curriedFn() is called to merge the last argument with (3), and arg.length==func.length is called to pass all arguments to it.

  • Corrification allows us to pass in fewer arguments to a function and get a new function that has some fixed memory
  • This is a cache of function parameters
  • Make the function more flexible, make the function smaller granularity
  • You can convert multivariate functions into unary functions, and you can combine functions to produce powerful functions.

Function composition

Compose: If a function needs to be processed by multiple functions to get the final value, you can combine the intermediate functions into a single function. A function is like a pipeline of data, and a combination of functions connects these pipes, allowing data to pass through multiple pipes to form the final result. Function combinations are executed from right to left by default.

  • Pure functions and Currization are easy to write onion code h(g(f(x)).
  • Function composition is the ability to recombine fine-grained functions to generate a new function

The following example demonstrates the combination of functions

function compose(f, g) {
    return function (value) {
        returnf(g(value)); }}/* Demonstrates the use of function combinations */
function reverse(arr) {
    return arr.reverse();
}

function first(arr) {
    return arr[0];
}

const last = compose(first,reverse);
console.log(last([1.2.3.4.5]));
Copy the code

Combinatorial functions in Lodash are combined using the flowRight method, executed from right to left

const _ = require('lodash');

const reverse = arr= > arr.reverse();

const first = arr= > arr[0];

const toUpper = s= > s.toUpperCase();

const l = _.flowRight(toUpper, first, reverse);

console.log(l(['a'.'b'.'c'.'d'.'e']));
Copy the code

Reverse (); flowRight (); flowRight (); flowRight () The reduce method iterates through the array passing the value of the previous array element to the next. In this way we implement combinatorial functions where the value of the previous function is passed to the next function.

// Implement flowRight
function compose(. args) {
    console.log(args);
    return function (value) {
        return args.reverse().reduce(function (acc, fn) {
            returnfn(acc); }, value); }}// Get the last element of the array and convert it to uppercase. Note that functions are run from right to left
const l = compose(toUpper, first, reverse);
Copy the code

arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])

The first accumulator accumulates the return value of the callback; It is the cumulative value returned the last time the callback was called

The element being processed in the second parameter array.

Compose short: Simplify the code with the ES6 arrow function

const compose = (. args) = > (value) => args.reverse().reduce((acc, fn) = >
    fn(acc), value);//reduce the second argument is an initial value reduce traverses all arrays such as summing up the first one and passing it to the second one
Copy the code
  • The associative property of a combination of functions is that you can combine g with h, or you can combine f with g, and you get the same result
let f = compose(f,g,h);
let a = compose(compose(f,g),h) == compose(f,compose(g,h))

/ / the associative law
constf = _.flowRight(_.flowRight(_.toUpper,_.first),_.reverse); = = =const f = _.flowRight(_.toUpper,_.flowRight(_.first,_.reverse));

console.log(f(['a'.'b'.'c'.'d'.'e']));
Copy the code

How to debug composite functions

How do you debug composite functions? I want to print the results of the a method, for example, actually deal with very simple we just need to want to print a method of execution result method append a trace, trace method is to provide printing method, in this method can get a method return values so that you can print the result of the last one method, as shown in the following code:

/* Function combination debugging */
//NEVER SAY DIE => never-say-die

const _ = require('lodash');
//_.split();

const split = _.curry((sep, str) = > {
    return _.split(str, sep);
});

//toLower join
const join = _.curry((sep, arr) = > {
    return _.join(arr, sep);
});

const trace = _.curry((tag,v) = >{
    console.log(tag,v);
    return v;
});

const map = _.curry((func,arr) = >{
    return _.map(arr,func);
})

const f = _.flowRight(join(The '-'),trace('map'), map(_.toLower),trace('split'),split(' '));

console.log(f('NEVER SAY DIE'));
Copy the code

Lodash/fp modules

  • Lodash’s FP module provides a practical approach to functional programming friendly.
  • Provides immutable auto-curried iteratee-first data-last method function priority data lag.

To solve the above problem of using Curry for currying, some native methods are to pass the data first and then pass the callback function, while FP module is to solve this problem and delay the data. (PS: Different languages and frameworks are designed to solve problems. Remember that programmers are designed to solve problems.)

The following code, the general common methods such as map () the first parameter is need to pass data to perform, but this will not be able to do the processing of curry, then by Mr Currie must change the method to wrap a layer of the following code: this is a very bad design, so whether loadsh provides such solution? The answer is yes let’s look at the FP module


const _ = require('lodash');

//_.split();

const split = _.curry((sep, str) = > {
    return _.split(str, sep);
});

//toLower join

const join = _.curry((sep, arr) = > {
    return _.join(arr, sep);
});

const log=function(v){
    console.log(v);
    return v;
}

const trace = _.curry((tag,v) = >{
    console.log(tag,v);
    return v;
});

const map = _.curry((func,arr) = >{
    return _.map(arr,func);
})

const f = _.flowRight(join(The '-'),trace('map'), map(_.toLower),trace('split'),split(' '));

console.log('?? ',f('NEVER SAY DIE'));
Copy the code

In the following code, the FP module pairs map, join, and split, with function priority data lag

const fp = require('lodash/fp');
const f = fp.flowRight(fp.join(The '-'),fp.map(fp.toLower),fp.split(' '));

console.log(f('NEVER SAY DIE'));//never_say_die
Copy the code

Map method difference and FP module

The following code converts an array element to Number in _.map, but the result is 23 NaN 2. parseInt(s: string, radix? So there is a problem that causes 2 to be converted to base 2, and fp module map only passes one parameter to parseInt

console.log(_.map(['23'.'8'.'10'].parseInt));//23 NaN 2
//parseInt('23',0,array)
//parseInt('8',1,array)
//parseInt('10',2,array)

// Fp modules do not have this problem
// The fp map function takes only one parameter, which is the processing parameter
console.log(fp.map(parseInt['23'.'8'.'10']));/ / 23 August 10
Copy the code

PointFree

The process of data processing can be defined as a composite operation that is independent of the data and does not need to use the parameter representing the data, as long as the simple operation steps are combined together. Before using this pattern, some auxiliary basic operation functions need to be defined.

  • There is no need to specify the data to be processed
  • You just need to synthesize the operation
  • You need to define some auxiliary basic operation functions

The PointFree pattern does not need to care about data

const f = fp.flowRight(fp.join(The '-'),fp.map(fp.toLower),fp.split(' '));
Copy the code

In fact, the PointFree pattern is a combination of functions that do not need to process data and return new functions to process data

//Hello world => hello_world

const fp = require('lodash/fp');

const f = fp.flowRight(fp.replace(/\s+/g.'_'),fp.toLower);// Function combinations do not need to process data
// Returns a new function to process the data
console.log(f('Hello world'));
Copy the code

Let’s write a case study to further understand the PointFree pattern

//world wild web => W,W,W
Map converts each element of the array to uppercase. Map takes the first letter of each element of the array
const firstLetterToUpper = fp.flowRight(fp.join(', '),
fp.map(fp.flowRight(fp.first,fp.toUpper)),fp.split(' '));

console.log(firstLetterToUpper('world wild web'));
Copy the code

Functor (Functor)

How to control side effects in functional programming under control, exception handling, asynchronous operations, etc. These problems introduce the idea of functors

Fuctor functor

  • Container: contains values and deformation relations of values (the deformation relations are functions)
  • Functor: a special container implemented by an ordinary object that has a map method that runs a function to manipulate values (deformation relationships)

The functor maintains a value inside, which is never exposed to the outside world. The value is processed through the map method, which is called in a chain.

class Container {
    static of(value) {
        return new Container(value);
    }
    constructor(value) {
        this._value = value;
    }

    map(fn) {
        return Container.of(fn(this._value)); }}let r = Container.of(5)
    .map(x= > x + 1)
    .map(x= > x * x);
console.log(r);//Container { _value: 36 }
Copy the code

Conclusion:

  • The operations of functional programming do not operate directly on values, but are performed by functors
  • A functor is a contract object that implements a Map
  • You can think of a functor as a box that encapsulates a value
  • To process a value in a box, you need the box’s map method to pass a function (pure function) that processes the value
  • The final map method returns a box containing the new value (functor)

The problem is that there is an exception when null is entered, and the exception cannot be handled. Let’s move on

// show the problem with null undefined
Container.of(null).map(x= >x.toUpperCase());//TypeError: Cannot read property 'toUpperCase' of null
Copy the code

MayBe functor

The MayBe functor is used to deal with external controls

class MayBe {
    static of(value) {
        return new MayBe(value);
    }

    constructor(value) {
        this._value = value;
    }

    map(fn) {
        return this.isNoting() ? MayBe.of(null) : MayBe.of(fn(this._value));
    }

    isNoting() {
        return this._value === null || this._value === undefined; }}// let r = MayBe.of('hello world').map(x => x.toUpperCase());
// let r = MayBe.of(null).map(x => x.toUpperCase()); //MayBe { _value: null }
let r = MayBe.of('hello world')
    .map(x= > x.toUpperCase())
    .map(x= > null)
    .map(x= > x.split(' '));//MayBe {_value: null} but where is the problem? It's impossible to know

// Maybe functor problem

console.log(r);
Copy the code

The MayBe functor checks inside the container to see if the value is empty and returns an empty functor if it is. But the MayBe functor has no way of knowing where a problem is, such as handling an exception problem, which leads to the next concept.

Either functor

Either of the two, like if… else… Processing. Exceptions make functions impure. Either functors can be used to handle exceptions.

The following code defines two functors, one to handle the correct result and one to handle the exception result, which returns this directly

class Left {
    constructor(value) {
        this._value = value;
    }

    static of(value) {
        return new Left(value);
    }

    map(fn) {
        return this; }}class Right {
    constructor(value) {
        this._value = value;
    }

    static of(value) {
        return new Right(value);
    }

    map(fn) {
        return Right.of(fn(this._value)); }}Copy the code

Note that the same input is different output in two functors

let r1 = Right.of(12)
    .map(x= > x + 2);

let l1 = Left.of(12).map(x= > x + 2);

console.log(r1,l1);//Right { _value: 14 } Left { _value: 12 }
Copy the code

To demonstrate the handling of an exception, the following code calls the Left functor in a catch and returns an error

function parseJson(str){
    try {
        return Right.of(JSON.parse(str))
    } catch (e) {
        // Use Left when an error occurs because the same input produces the same output
        return Left.of({error:e.message}); }}// Exception handling
let r = parseJson('{ "name":"zs" }');

console.log(r);//Left { _value: { error: 'Unexpected token n in JSON at position 1' } }
Copy the code

Normal result processing, through. Map for further processing of the next business logic

// In the correct case
let r = parseJson('{ "name":"zs" }').map(x= >x.name.toUpperCase());// Process json to convert the name property to uppercase
console.log(r);//Right { _value: { name: 'ZS' } }
Copy the code

IO functor

The _value in the IO functor is a function, which is treated as a value; IO functors can store impure actions in _value, delay the execution of the impure operation (lazy execution), wrap the current operation and pass the impure operation to the caller

/ / IO functor
const fp = require('lodash/fp');

class IO {
    static of(value) {
        return new IO(function () {
            return value;
        });
    }
    constructor(fn) {
        this._value = fn;
    }
    map(fn){
        return new IO(fp.flowRight(fn,this._value)); }}/ / call
let io = IO.of(process).map(p= >p.execPath).map(p= >p.toUpperCase());
console.log(io);
P => p.toupperCase () Note the order in which the map functions are executed
console.log(io._value());/ / / Users/prim/NVM/versions/node/v12.14.0 / bin/node execution method
/ / / USERS/PRIM/NVM/VERSIONS/NODE/V12.14.0 / BIN/NODE
Copy the code

Folktale

Folktale is a standard functional programming library. The implementation of asynchronous tasks is too complex to be demonstrated using Tasks in Folktale. It provides only a few functionally handled operations: compose, Curry, some functor tasks, Either, Maybe, etc

The Task functor handles asynchronous tasks

const { compose, curry } = require('folktale/core/lambda');
const { toUpper, first,split,find } = require('lodash/fp');
const { task } = require('folktale/concurrency/task');
const fs = require('fs');
let f = curry(2, (x, y) => {
    return x + y;
})

console.log(f(1.2));/ / 3
console.log(f(1) (2));/ / 3

//compose function combination

let f1 = compose(toUpper, first);

console.log(f1(['one'.'two']));//ONE

function readFile(filename) {
    return task(resolver= > {
        fs.readFile(filename, 'utf-8', (err, data) => {
            if(err) { resolver.reject(err); } resolver.resolve(data); })}); } readFile('package.json')
    .map(split('\n'))
    .map(find(x= >x.includes('version')))
    .run()/ /?? What's the use of run? What code is being executed? Do YOU return the above results to LISTEN?
    .listen(
        {
            onRejected:err= >{
                console.log(err);
            },
            onResolved:data= >{
                console.log(data); }});Copy the code

Pointed functor

The conservative functor is a functor that implements the static of method, which is used to avoid using new to create objects. The deeper point is that the of method is used to put values into Context (put values into containers and use maps to process values).

In fact, the above functors are all conservative functors.

Monad functor

IO functor problem, in the business logic encountered functor nesting IO(IO(x)); Monad is the solution to the functor nesting problem.

let readFile = function (filename) {
    return new IO(function () {
        return fs.readFileSync(filename, 'utf-8');
    });
}

let print = function (log) {
    return new IO(function(){
        console.log(log);
        return log;//log = IO(x)
    });
}

let cat = fp.flowRight(print,readFile);

let r = cat('package.json')._value()._value(); // IO(IO(x))
console.log(r);//IO { _value: [Function] }
Copy the code
  • The Monad functor is a Pointed functor that can be flattened
  • A functor is a Monad if it has both join and of methods and obeies some laws
const fp = require('lodash/fp');
const fs = require('fs');
class IO {
    static of(value) {
        return new IO(function () {
            return value;
        });
    }
    constructor(fn) {
        this._value = fn;
    }
    map(fn) {
        return new IO(fp.flowRight(fn, this._value));The merge function returns a new functor
    }
    join(){
        / / call _value
        return this._value();
    }
    flatMap(fn){
        return this.map(fn).join();// Execute the merge function}}let readFile = function (filename) {
    return new IO(function () {
        return fs.readFileSync(filename, 'utf-8');
    });
}
let print = function (log) {
    return new IO(function(){
        console.log(log);
        return log;//log = IO(x)
    });
}
let r = readFile('package.json')//_value = fn1
    .map(x= >x.toUpperCase())// Process file _value=fn11
    .flatMap(print)//return IO(value) ==> _value = fp.flowRight(print,fn11,fn1); value = _value();
    .join(); // map(fn2) _value = fn2=new IO() ,fn1 join():_value: fp.flowRight(fn2, fn1) => new IO(fn3); ---> join:fn3()
console.log(r);//IO { _value: [Function] }
Copy the code

conclusion

  • Functional programming does not improve performance because heavy use of closures can degrade performance to some extent
  • Functions in functional programming are not functions and methods in programs, but functions in mathematics
  • Functional first-class citizen (MDN interpretation only includes these three points)
    • Functions can be stored in variables
    • Functions can be arguments
    • A function can be a return value
  • Side effects can make a function impure, but side effects are inevitable, because the code will inevitably depend on external files, databases, etc., and can only be controlled as much as possible
  • The Curry function is also of higher order
  • The currization function uses closures internally to cache the parameters of the function
  • A Coriolization function can transform a function with multiple parameters into a function with only one parameter, resulting in a more powerful function through combination
  • Currization makes functions more flexible and smaller in granularity
  • A function can be thought of as a pipeline for processing data, in which the input parameter x is processed to produce the result Y
  • Multiple unary functions can be combined into a more powerful function through function composition
  • Function combinations must satisfy the associative law. By default, function combinations are executed from right to left
  • A functor is a special container (object) that encapsulates a value inside and passes a function through a map to process the value
  • The MayBe functor is used to handle external null cases and prevent null exceptions
  • The value encapsulated inside an IO functor is a function that encapsulates impure operations into the function, leaving impure operations to the caller
  • The value encapsulated inside a Monad functor is a function (which returns a functor) that is intended to avoid functor nesting through the join method