Working people! For the soul! It’s the front end! This series is summed up in the process of learning in the road of big front attack. If there is something wrong in the article, I hope you can criticize and correct it and make progress with each other.

Functional programming concepts

What is functional programming

  • Functional Programming (FP), is a Programming style, can also be considered as a mode of thinking, and process oriented, object-oriented is parallel.
  • Functional programming is the abstraction of the operation process. The function is not the function or method in the program, but the function mapping in mathematics. For example, y=cos(x) is the relation between y and x.
  • In functional programming, the same input must produce the same output, also known as a pure function.
  • In functional programming, we abstract functions into fine-grained functions and combine those functions into more powerful ones.
// Non-functional programming
let num1 = 2
let num2 = 3
let sum = num1 + num2
console.log(sum)

// Functional programming
function add(n1, n2) {
    return n1 + n2
}
let sum = add(2.3)
console.log(sum)
Copy the code

Why learn functional programming

  • Functional programming does not depend on or change external state; the same input must return the same output. Therefore, each function is independent, which facilitates unit testing and troubleshooting, as well as reuse of functions.
  • As Vue and React became more popular, functional programming became more popular. Ecology is also very good, for example: Lodash, Ramda, etc.
  • There is also the ability to read functional code written by others ~

Functional programming basics – functions

The function is a first-class citizen

MDN portal: MDN first-class Function

In JavaScript, “everything is an object”, so a function is also an object in JS. We can store functions in variables or arrays. Functions can also be used as parameters and return values of other functions.

  • Functions can be stored in variables or arrays
// Copy the function to the variable
let sayHi = function () {
    console.log("hi")}Copy the code
  • Functions can be passed as arguments
// Customize the forEach function
// Iterate over each item in the array and process each item accordingly
function forEach (array,fn) {
    for(let item of array) {
        fn(item)
    }
}
/ / test
let arr = [1.2.3]
let func = function (value) {
    value = value * 2
    console.log(value) 
}
forEach(arr,func)   / / 2 4 6
// It is also possible to write more succinctly in the following way
forEach(arr,item= > {
    item = item * 2
    console.log(item) / / 2 4 6
})
Copy the code
  • Function as the return value
// function as return value
function sayHi () {
    let msg = 'hi'
    return function () {
        console.log(msg)
    }
}
/ / use
const hi = sayHi() Function () {console.log(MSG)} function () {console.log(MSG)}
// So there are two ways to call it
hi() // hi
sayHi()()//hi
Copy the code

Higher order functions

What is a higher-order function?

  • Function as argument
  • Function as the return value

Meaning of higher order functions

Higher-order functions are used to abstract general problems, and abstractions can help us focus on goals and functions that solve such problems without worrying about implementation details.

In plain English, we have an array [1,2,3]. Now we want to square each item of the array and return a new array [1,4,9]. The original idea is to square each item of the array and return the array. But what if we want to return an array with an array [2,3,4] incremented by one? We need cc+ CV to fix it, and it’s a lot of trouble to fix it, but after we use higher-order functions we can implement the function and just focus on the function that solves the problem.

// The map function iterates over each element in the array and returns the result in a new array
const map = (array, fn) = > { 
    let results = [] 
    for (const value of array) { 
        results.push(fn(value)) 
    }
    return results 
}
/ / use
let arr = [1.2.3]
arr = map(arr, v= > v * v)
console.log(arr) / / 1 4 9
// If we want to return the array plus one, we don't need to modify the map function, just focus on the implementation function
let arr1 = [1.2.3]
arr1 = map(arr1, v= > v + 1)
console.log(arr1) / / 2, 3, 4
Copy the code

Commonly used higher-order functions

Common features: taking functions as arguments

  • forEach
  • map
  • filter
  • some
  • .

Third, the closure

The concept of closures

Closure: A Closure is formed by binding a function with references to its surrounding state (lexical context).

  • You can call an inner function of a function in a function and access members of that function’s scope. We have already used closures in the above function as a return value.
// If sayHi is executed, MSG will be released
function sayHi () {
    let msg = 'hi'
}

// In this case, however, a function is returned and a member inside the original function is accessed in the returned function, which forms a closure
function sayHi () {
    let msg = 'hi'
    return function () {
        console.log(msg)
    }
}
// hi() is an external function. If an external function has a reference to an internal member, the internal member MSG cannot be freed.
const hi = sayHi() 
hi() // hi
/ / note
We can call sayHi's internal function in another scope
// 2. We can access the inner member when we call the inner function

Copy the code

The core role of closures

Extends the scope of a function’s internal members

The nature of closures

Functions are placed on an execution stack during execution and removed from the stack when they are finished. However, scoped members on the heap cannot be freed because they are referenced externally, so the inner function can still access the members of the outer function.

The function is on the execution stack at the time of execution, and is removed from the execution stack after execution, freeing the memory of the internal member. However, after the function is removed, the memory of the internal member cannot be freed if there is an external reference. /

Closure view

You can use the Chrome debugger tool for breakpoint debugging to see when and where closures occur.

Pure functions

Pure function concept

The same input will always get the same output, without any observable side effects.

A pure function is like a function in mathematics, y = sin(x).

  • Functional programming does not preserve the middle of the calculation, so variables are immutable (stateless)
  • We can also hand over the results of one function to another
let numbers = [1.2.3.4.5] 
/ / pure functions
// The output is the same for the same function

// The slice method returns the truncated function without affecting the original array
numbers.slice(0.3) // => [1, 2, 3] 
numbers.slice(0.3) // => [1, 2, 3] 
numbers.slice(0.3) // => [1, 2, 3] 

// impure function
// For the same input, the output is different

// the splice method returns the array and changes the array
numbers.splice(0.3) // => [1, 2, 3] 
numbers.splice(0.3) / / = > [4, 5]
numbers.splice(0.3) / / = > []

// This is also a pure function
function getSum (n1, n2) {
    return n1 + n2
}
console.log(getSum(1.2)) / / 3
console.log(getSum(1.2)) / / 3
console.log(getSum(1.2)) / / 3
Copy the code

Lodash – A library of pure functions

Lodash website

Lodash Chinese documentation

Lodash is a pure function library that provides modularity, high performance, and additional functionality. Provides methods for operating on arrays, numbers, objects, strings, functions, and more

Using Lodash

  • The installation

NPM init -y → NPM I lodash

  • use
const _ = require('lodash')

const array = ['jack'.'tom'.'lucy'.'kate']

// The alias of head is first. Head (array) can also be used
console.log(_.first(array)) //jack
console.log(_.last(array)) //kate
Copy the code

The benefits of pure functions

cacheable

Because there is always the same result for the same input, you can cache the results of pure functions to improve performance.

const _ = require('lodash')

function getArea(r) {
  console.log(r)
  return Math.PI * r * r
}

let getAreaWithMemory = _.memoize(getArea)
console.log(getAreaWithMemory(4))
console.log(getAreaWithMemory(4))
console.log(getAreaWithMemory(4))
/ / 4
/ / 50.26548245743669
/ / 50.26548245743669
/ / 50.26548245743669
// See that the output 4 is executed only once, because the result is cached
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 to access shared memory data, so they can be run arbitrarily in parallel
  • Although JS is single-threaded, ES6 has a Web Worker that can start a new thread

Side effects

The side effect is to make a function impure. Pure functions return the same output based on the same input. If the function depends on the external state, it cannot guarantee the same output, which can cause side effects, such as the following example:

// Impure function, because it depends on the external variable mini, which changes the same input to get a different output
let mini = 18 
function checkAge (age) { 
    return age >= mini 
}

// Pure function, but hard coded, can be solved by currization.
let mini = 18 
function checkAge (age) { 
    return age >= mini 
}
Copy the code

Sources of side effects:

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

All external interactions are likely to bring side effects, which also make methods less versatile and less suitable for scaling and reusability. Side effects can also bring security risks and uncertainties to programs, but side effects cannot be completely banned. We can only control them as much as possible and keep them under control.

Currying

Let’s first solve the problem of hard coding in the previous example by using Coriolization

// The following code solves the problem of impure functions, but it is hard coded
function checkAge (age) { 
    let mini = 18
    return age >= mini 
}


// a plain pure function
function checkAge (min, age) {
    return age >= min
}
console.log(checkAge(18.20))  //true
console.log(checkAge(18.24))  //true
console.log(checkAge(20.24))  //true

/ / curry
The checkAge function returns a function that compares the age with the minimum age in the new function.
function checkAge (min) {
    return function (age) {
        return age >= min
    }
}

/ / ES6 writing
let checkAge = min= > (age= > age >= min)

// Use checkAge to return 18 and 20
let checkAge18 = checkAge(18)
let checkAge20 = checkAge(20)
// Use the 18 and 20 functions returned to accept the age to determine true or false
console.log(checkAge18(20)) //true
console.log(checkAge18(24)) //true
Copy the code
  • Currying

    When a function has multiple arguments, we can modify the function. The modified function can first pass some parameters (which will never change) and then return a new function. The new function passes the rest of the arguments and returns the result.

Currization in Lodash — Curry ()

_.curry(func)

  • Function: creates a function that takes one or more arguments to func, executes func if all required arguments are provided and returns the result of the execution. Otherwise continue to return the function and wait for the remaining arguments to be received.
  • Parameters: functions that require currization
  • Return value: The currified function
const _ = require('lodash')

// The parameters are one unary function and two binary functions
// It is possible to transform a function of several variables into a function of one variable
function getSum (a, b, c) {
  return a + b + c
}

// Define a Currization function
const curried = _.curry(getSum)

// If all parameters are entered, the result is returned immediately
console.log(curried(1.2.3)) / / 6

// If a partial argument is passed in, it returns the current function and waits to receive the remaining arguments in getSum
console.log(curried(1) (2.3)) / / 6
console.log(curried(1.2) (3)) / / 6
Copy the code

case

Match (/\s+/g) : “. Match (/\s+/g) : “. Match (/\s+/g)

function match(reg, str) {
  return str.match(reg)
}
// We pass in two arguments at a time, and most of the time reg regular expressions are immutable, so we need to corrify this function

// Currization
const _ = require('lodash')

// Using Lodash's curry function, the first argument is a matching rule, and the second argument is a string, to generate a match function
const match = _.curry(function (reg, str) {
  return str.match(reg)
})

// haveSpace is a function that matches Spaces according to the rule
const haveSpace = match(/\s+/g)

console.log(haveSpace("hello world")) / / / '
console.log(haveSpace("helloworld")) //null
// Check if there are Spaces in the string
Copy the code

So now we want to check if there are Spaces in the string, and now we want to check if there are numbers in the string what do we do?

// By rule haveNumber is a function that matches numbers
const haveNumber = match(/\d+/g)
console.log(haveNumber('abc')) // null
Copy the code

This is for strings, now we want to match against arrays

// How do arrays match elements with Spaces
const filter = _.curry(function(func, array) {
  return array.filter(func)
})
// The filter function, the first argument passes whether there is a space in the matched element
// The second argument is the specified array
console.log(filter(haveSpace, ['John Connor'.'John_Donne'])) // [ 'John Connor' ]

// Encapsulate the function as a function
// Filter can pass a parameter and return a function
// findSpace is a function that matches if there is a space in an array element
const findSpace = filter(haveSpace)
console.log(findSpace(['John Connor'.'John_Donne'])) // [ 'John Connor' ]
Copy the code

Summary of case ideas

The nice thing about currization is that we can reuse our functions the most.

const _ = require('lodash')

// The match function returns the result of a match based on a string of re's
const match = _.curry(function (reg, str) {
  return str.match(reg)
})

//haveSpace is a function that matches Spaces
const haveSpace = match(/\s+/g)

// The haveNumber function is a function that matches numbers
const haveNumber = match(/\d+/g)

The filter function defines an array and a filter rule, and returns an array that matches the matching rule
const filter = _.curry(function(func, array) {
  return array.filter(func)
})

The findSpace function is a function that matches an array element with a space and returns an array that matches that
const findSpace = filter(haveSpace)
Copy the code

Simulation of the Currization principle

Let’s take an example from before

const _ = require('lodash')

function getSum (a, b, c) {
  return a + b + c
}

const curried = _.curry(getSum)

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

The idea of implementing a Currie transformation function:

  1. Input argument Output argument: The call passes a pure function argument and returns a Corrified function when completed
  2. Analysis of input parameters:
    • If the currid call passes the same number of arguments as the getSum call, it is executed immediately and returns the result of the call
    • If the currid call passes a partial argument to getSum, a new function is returned and waits to receive the other arguments to getSum
  3. We need to focus on getting the parameters of the call and determining whether the number of arguments and parameters is the same.
// Simulate the Currization function
function curry (fun) {
    // The name is called when the following argument is less than the parameter
    // The args argument is an array. an
    return function curryFn(. args) {
        // Check whether the number of arguments and parameters is the same
        if(args.length < fun.length){
            return function () {
                If the number of remaining arguments plus the number of previous arguments equals the parameter, return fun
                // The first part of the argument is in args, the second part of the argument is in arguments, merge the two and expand
                returncurryfn(... args.concat(Arrary.from(arguments)))}}// The number of arguments is greater than or equal to the number of parameters
        returnfun(... args) } }Copy the code

Corrified summary

  • Corrification allows us to pass fewer arguments to a function and get a new function that remembers some of the fixed arguments (for example, the match function is regenerated as a haveSpace function that uses closures and remembers the arguments to the regular expression that we passed).
  • This is a ‘cache’ of function arguments (using closures)
  • 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

  • Pure functions and Currization are easy to write onion code h(g(f(x)).

    • Gets the last element of the array before converting to uppercase

      _.toUpper(_.first(_.reverse(array)))
      Copy the code

Look at these brackets! Doesn’t it look like an onion in layers.

To avoid code becoming onion code, we need to use function composition to recombine fine-grained functions to generate a new function.

The pipe

As shown in the figure above, it represents the process of using functions to process data in the program, input parameter A to fn function and return result B. You can think of the fn function as a pipe, through which a data flows to B data.

When the fn function is complicated, we can divide the function fn into several smaller functions, and there are more m and N generated in the intermediate operation process. As shown in the figure above, we can split the FN function pipe into F1, F2 and F3 pipes. Data A gets M through F3 pipe, M gets N through F2 pipe, and N finally gets DATA B through F1 pipe.

Function combination concept

  • Function composition (compose)If a function needs to be processed by multiple functions to get the final value, it is possible to 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
// function combination demo
function compose(f, g) {
  return function (value) {
    return f(g(value))
  }
}

// Array flipping function
function reverse (array) {
  return array.reverse()
}

// Get the first element of the function function
function first (array) {
  return array[0]}// Get the last element of the function
const last = compose(first, reverse)

console.log(last([1.2.3.4])) / / 4
Copy the code

But here we can only combine two functions, so what do we do if we have more than one function to combine? Keep reading.

Combinatorial functions in Lodash

Lodash combines the flow() or flowRight() functions, both of which can combine multiple functions.

  • Flow () runs from left to right
  • FlowRight () runs from right to left and is used more often

Here is an example of using function composition in LoDash

const _ = require('lodash')

const reverse = arr= > arr.reverse()
const first = arr= > arr[0]
const toUpper = s= > s.toUpperCase()

const f = _.flowRight(toUpper, first, reverse)

console.log(f(['one'.'two'.'three'])) // THREE
Copy the code

FlowRight () principle simulation

Let’s examine the flowRight() function first:

  • The number of arguments passed in is indeterminate, and they are all functions. The output argument is a function that has a cook parameter value
function compose (. args) {
    // Use...
    //args is an array of function names
    return function (value) {
        // reduce: Executes the provided function for each element in the array and sums it up as a result
        // We need to reverse the args array because it is executed from right to left by default
        //acc defaults to value. Fn is the name of each function in the args array
        return args.reverse().reduce(function (acc,fn){
            return fn(acc)
        },value)
    }
}

/ / test
const fTest = compose(toUpper, first, reverse)
console.log(fTest(['one'.'two'.'three'])) // THREE

// You can use ES6 to simplify the code
const compose = (. args) = > (value) = >
	args.reverse().reduce((arr, fn) = > fn(arr), value);
Copy the code

Associative law of functions

What is associative law of functions

It doesn’t matter if we reverse the order of the combinations of functions is associative. Such as:

// Associativity
let f = compose(f, g, h) 
let associative = compose(compose(f, g), h) == compose(f, compose(g, h)) 
// true
Copy the code
const _ = require('lodash')

/ / way
const f = _.flowRight(_.toUpper, _.first, _.reverse)
2 / / way
const f = _.flowRight(_.flowRight(_.toUpper, _.first), _.reverse)
3 / / way
const f = _.flowRight(_.toUpper, _.flowRight(_.first,  _.reverse))

// Print the same result as THREE
console.log(f(['one'.'two'.'three'])) // THREE
Copy the code

Debugging of function combinations

When we combine multiple functions and the result is different from what we expected, how do we debug to find out what went wrong? How do we know the outcome of each step in between?

Go straight to chestnuts

If we want to convert NEVER SAY DIE to nerver-say-die and print it out, we can first spell the string into an array of whitespace characters, then convert uppercase characters to lowercase characters, and finally spell each item in the array with -.

const _ = require('lodash')

// The split function takes two arguments, and the last time we call it we pass a string, so the string is passed in the second position, so we need to wrap a split function ourselves
// _.split(string, separator)

// Convert multiple arguments to a single argument, using the currization of the function
const split = _.curry((sep, str) = > _.split(str, sep))

Use toLower(). Since this function has only one argument, it can be used directly in function combinations

The first parameter is an array, the second parameter is a delimiter, and the array is also passed last
const join = _.curry((sep, array) = > _.join(array, sep))

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

console.log(f('NEVER SAY DIE')) //n-e-v-e-r-,-s-a-y-,-d-i-e
Copy the code

We follow the idea, but the final print found that the actual output is not consistent with the expected, at this time, how should we debug?

// NEVER SAY DIE --> nerver-say-die
const _ = require('lodash')
 
const split = _.curry((sep, str) = > _.split(str, sep))
const join = _.curry((sep, array) = > _.join(array, sep))

// We need to print the intermediate value and know its position
const log = _.curry((tag, v) = > {
  console.log(tag, v)
  return v
})

// Add a log to each function from right to left, and pass in the value of the tag
const f = _.flowRight(join(The '-'), log('after toLower:'), _.toLower, log('after split:'), split(' '))
// From right to left
// first log: after split: ['NEVER', 'SAY', 'DIE'
// The second log: after toLower: never,say,die is converted to a string
console.log(f('NEVER SAY DIE')) //n-e-v-e-r-,-s-a-y-,-d-i-e


// Use the map method of the array to iterate over each element of the array and make it lowercase
// Map takes two arguments, the first is an array, and the second is a callback function, which needs to be currified
const map = _.curry((fn, array) = > _.map(array, fn))

const f1 = _.flowRight(join(The '-'), map(_.toLower), split(' '))
console.log(f1('NEVER SAY DIE')) // never-say-die
Copy the code

Lodash FP module

In the example above, we used split, join, map and other functions that need to be currized and the order of parameters reversed. Lodash thought of this in advance and provided FP modules.

  • Lodash’s FP module provides many methods that are friendly to functional programming
  • Provides immutable auto-curried iteratee-first data-last (function first, data last) methods
// Lodash module
const _ = require('lodash')
// data first, function after
_.map(['a'.'b'.'c'], _.toUpper) 
// => ['A', 'B', 'C'] 
_.map(['a'.'b'.'c']) 
// => ['a', 'b', 'c'] 

// Set data first, set rules later
_.split('Hello World'.' ') 


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

// function first, data second
fp.map(fp.toUpper, ['a'.'b'.'c'])
fp.map(fp.toUpper)(['a'.'b'.'c']) 
// set rules first and data later
fp.split(' '.'Hello World') 
fp.split(' ') ('Hello World')
Copy the code

Point Free

A style of programming in which the implementation is a combination of functions, such as the one used in our example above.

Point Free: We can define the process of data processing as a composite operation unrelated to data, without using the parameter representing data, as long as the simple operation steps are combined together. Before using this mode, we need to define some auxiliary basic operation functions.

  • 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
// Non-point Free mode
// Hello World => hello_world
function f (word) {
    return word.toLowerCase().replace(/\s+/g.The '-')}// Point Free style
const fp = require('lodash/fp') 
const f = fp.flowRight(fp.replace(/\s+/g.The '-'),fp.toLower)
console.log(f('Hello World'))
Copy the code

Functor (Functor)

Why should we learn functors

Representative functor (REPRESENTATIVE functor) is a concept in category theory, which refers to a special functor from arbitrary category to set category. There is no way to avoid side effects, but we can try to keep them under control, we can handle side effects through functors, we can handle exceptions, asynchronous operations and so on through functors.

What is a 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)

Understand Functor

class Container {
    // of static method that returns a new Container and omits the new keyword when creating objects externally
  static of (value) {
      return new Container(value)
  }
  constructor (value) {
    // The value of this functor is kept internally
    Members underlined by _ are private members and cannot be accessed externally. The values are initialized as passed parameters
    this._value = value
  }
  
  // There is an external method, map, that receives a function to handle the value
  map (fn) {
    // Return a new functor, which will hold the value handled by fn
    return Container.of(fn(this._value))
  }
}

// Create a functor object
let r = Container.of(5)
  .map(x= > x + 1) / / 6
  .map(x= > x ** 2) / / 36

// Return a container functor object with a value of 36
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 an object that implements the Map contract
  • We can think of a functor as a box that encapsulates a value
  • To process the values in the box, we need to pass a value-handling function (pure function) to the box’s Map method, which will process the values
  • The final map method returns a box containing the new value (functor)

Note: In Functor, what if we pass null or undefined by accident?

// Value accidentally passed null or undefined(side effect)
Container.of(null)
  .map(x= >x.toUpperCase()) // An error was reported, making the function impure
Copy the code

Maybe functor

  • We may encounter many errors in the process of programming, and we need to deal with these errors accordingly
  • The MayBe functor is used to handle external null cases (to keep side effects within permissible limits).
class MayBe {
  static of (value) {
    return new MayBe(value)
  }
  constructor (value) {
    this._value = value
  }

  map(fn) {
    // Check whether the value of value is null or undefined. If so, return a functor with value null. If not, execute the function
    return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value))
  }

 // Define a function that checks whether it is null or undefined and returns true/false
  isNothing() {
    return this._value === null || this._value === undefined}}const r = MayBe.of('hello world')
  .map(x= > x.toUpperCase())

console.log(r) //MayBe { _value: 'HELLO WORLD' }


// If null is entered, no error will be reported
const rnull = MayBe.of(null)
  .map(x= > x.toUpperCase())
console.log(rnull) //MayBe { _value: null }
Copy the code

We use the Maybe functor to solve the problem of null or undefined, but if we make multiple map calls to the functor and it returns NULL, we don’t know in which step. We need to look at the next Either functor to solve this problem.

MayBe.of('hello world')
  .map(x= > x.toUpperCase())
  .map(x= > null)
  .map(x= > x.split(' '))
// => Maybe
Copy the code

Either functor

  • Either of the two, similar to if… else… The processing of
  • When there is a problem, the Either functor gives you valid information about the prompt,
  • Exceptions make functions impure, and Either functors can be used for exception handling
// We need to define both left and right functors

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

  constructor (value) {
    this._value = value
  }

  map (fn) {
    return this}}class Right {
  static of (value) {
    return new Right(value)
  }

  constructor (value) {
    this._value = value
  }

  map (fn) {
    return Right.of(fn(this._value))
  }
}

let r1 = Right.of(12).map(x= > x + 2)
let r2 = Left.of(12).map(x= > x + 2)
console.log(r1) // Right { _value: 14 }
console.log(r2) // Left { _value: 12 }
// Why is the result different? Because Left returns the current object, it does not use fn

// How to handle exceptions here?
// We define a function that converts a string to an object
function parseJSON(str) {
  // Use try-catch for possible errors
  // The Right functor is normally used
  try{
    return Right.of(JSON.parse(str))
  }catch (e) {
  // The Left functor is used after the error and an error message is returned
    return Left.of({ error: e.message })
  }
}

let rE = parseJSON('{name:xm}')
console.log(rE) // Left { _value: { error: 'Unexpected token n in JSON at position 1' } }
let rR = parseJSON('{"name":"xm"}')
console.log(rR) // Right { _value: { name: 'xm' } }

console.log(rR.map(x= > x.name.toUpperCase())) // Right { _value: 'XM' }
Copy the code

IO functor

  • IO is the input and output, and the _value in the IO functor is a function, which is treated as a value
  • IO functors can store impure actions in _value, delay execution of the impure operation (lazy execution), and wrap the current operation
  • Leave impure operations to the caller
const fp = require('lodash/fp')

class IO {
  // the of method creates IO quickly, returns a value to a function, and calls the function later when the value is needed
  static of(value) {
    return new IO(() = > value)
  }
  // A function is passed in
  constructor (fn) {
    this._value = fn
  }

  map(fn) {
    // A new constructor is used to combine the current _value function with the fn function passed in by map
    return new IO(fp.flowRight(fn, this._value))
  }
}


// test
// The node execution environment can pass a process object.
// Wrap the current value of the procedure in the function when calling of, and then retrieve the process when needed
const r = IO.of(process)
  // Map needs to pass in a function, and the function needs to receive an argument, which is the argument process passed in of
  // Return the execPath property in process, the execution path of the current Node process
  .map(p= > p.execPath)
console.log(r) // IO { _value: [Function] }


// The above is just the composition function, if need to call the following
console.log(r._value()) // C:\Program Files\nodejs\node.exe
Copy the code

Task functor (asynchronous execution)

  • Functors can control side effects and also handle asynchronous tasks in order to avoid the gates of hell
  • Instead of asynchronous tasks, we use tasks from Folktale
  • Folktale is a standard functional programming library. Unlike Lodash and Ramda, it doesn’t offer many features. It provides only a few functionally handled operations, such as compose, Curry, etc., some functor tasks, Either, MayBe, etc

The installation of the folktale

Start by installing the FolkTale library

npm i folktale
Copy the code

The Curry function in Folktale

const { compose, curry } = require('folktale/core/lambda')

// The first argument in Curry is that the function has several arguments, to avoid some errors
const f = curry(2.(x, y) = > x + y)

console.log(f(1.2)) / / 3
console.log(f(1) (2)) / / 3
Copy the code

The compose function in Folktale

const { compose, curry } = require('folktale/core/lambda')
const { toUpper, first } = require('lodash/fp')

// compose is flowRight in lodash
const r = compose(toUpper, first)
console.log(r(['one'.'two']))  // ONE
Copy the code

Task functors execute asynchronously

  • Folktale (2.3.2) Tasks in 2.x are quite different from tasks in 1.0, which is much closer to what we are demonstrating here

functor

  • This is illustrated in 2.3.2
const { task } = require('folktale/concurrency/task')
const fs = require('fs')
// in 2.0 is a function that returns a functor object
// 1.0 is a class

// Read the file
function readFile (filename) {
  // task passes a function with the argument resolver
  Resolver (reject); resolve (reject); resolver (reject); resolver (resolve
  return task(resolver= > {
    The first parameter is the path, the second parameter is the encoding, and the third parameter is the callback
    fs.readFile(filename, 'utf-8'.(err, data) = > {
      if(err) resolver.reject(err)
      resolver.resolve(data)
    })
  })
}

// demonstrate the call
// The readFile call returns the Task functor using the run method
readFile('package.json')
  .run()
  // Task listen can be used to listen for the result
  // Listen to an object, onRejected is listening to an error and onResolved is listening to the error
  .listen({
    onRejected: (err) = > {
      console.log(err)
    },
    onResolved: (value) = > {
      console.log(value)
    }
  })
 
 / * * {" name ":" Functor ", "version" : "1.0.0", "description" : ""," main ":" either. Js ", "scripts" : {" test ": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": {" folktale ":" ^ "2.3.2," lodash ":" ^ 4.17.20 "}} * /
Copy the code

case

Extract the Version field from package.json

const { task } = require('folktale/concurrency/task')
const fs = require('fs')
const { split, find } = require('lodash/fp')
// in 2.0 is a function that returns a functor object
// 1.0 is a class

// Read the file
function readFile (filename) {
  // task passes a function with the argument resolver
  Resolver (reject); resolve (reject); resolver (reject); resolver (resolve
  return task(resolver= > {
    The first parameter is the path, the second parameter is the encoding, and the third parameter is the callback
    fs.readFile(filename, 'utf-8'.(err, data) = > {
      if(err) resolver.reject(err)
      resolver.resolve(data)
    })
  })
}

// demonstrate the call
// The readFile call returns the Task functor using the run method
readFile('package.json')
  // Call the map method before run. The map method takes the file and returns the result
  // There is no implementation mechanism to think about when using functors
  .map(split('\n'))
  .map(find(x= > x.includes('version')))
  .run()
  // Task listen can be used to listen for the result
  // Listen to an object, onRejected is listening to an error and onResolved is listening to the error
  .listen({
    onRejected: (err) = > {
      console.log(err)
    },
    onResolved: (value) = > {
      console.log(value) / / "version" : "1.0.0",}})Copy the code

Pointed functor

  • The conservative functor is a functor that implements the static of method in order to avoid using new to create objects. The deeper implication is that the of method is used to put values into context
  • Context (putting values into containers, using maps to process values)
class Container { 
/ / Point functor
// Returns the value in a new functor, which is a context
    static of (value) { 
        return newThe Container (value)}... }// We get a context when we call of, and then we process the data in the context
Contanier.of(2)
 .map(x= > x + 5)
Copy the code

Monad functor (Monad)

IO functor nesting problem

  • Used to solve a problem of IO functor multi-layer nesting
const fp = require('lodash/fp')
const fs = require('fs')

class IO {
  static of (value) {
    return new IO(() = > {
      return value
    })
  }
  constructor (fn) {
    this._value = fn
  }

  map(fn) {
    return new IO(fp.flowRight(fn, this._value))
  }
}

// Read the file function
let readFile = (filename) = > {
  return new IO(() = > {
    // Synchronize file retrieval
    return fs.readFileSync(filename, 'utf-8')})}// Print the function
// x is the IO functor of the previous step
let print = (x) = > {
  return new IO(() = > {
    console.log(x)
    return x
  })
}

// Combine functions to read the file before printing it
let cat = fp.flowRight(print, readFile)
/ / call
// get the result of nested IO functor IO(IO(x))
let r = cat('package.json')
console.log(r) 
// IO { _value: [Function] }
console.log(cat('package.json')._value()) 
// IO { _value: [Function] }
// IO { _value: [Function] }
console.log(cat('package.json')._value()._value())
// IO { _value: [Function] }
/ * * * {" name ":" Functor ", "version" : "1.0.0", "description" : ""," main ":" either. Js ", "scripts" : {" test ": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": {" folktale ":" ^ "2.3.2," lodash ":" ^ 4.17.20 "}} * /
Copy the code

When we encounter multiple IO functors nested above, then _value will be called many times. Such call experience is not good, so we need to optimize.

What is a Monad functor

  • The Monad functor is a Pointed functor that can be flattened to solve the IO functor nesting problem, IO(IO(x))
  • A functor is a Monad if it has both join and of methods and obeies some laws

Implement a Monad functor

We mainly want to understand how monAD is implemented.

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

class IO {
  static of (value) {
    return new IO(() = > {
      return value
    })
  }
  constructor (fn) {
    this._value = fn
  }

  map(fn) {
    return new IO(fp.flowRight(fn, this._value))
  }

  join () {
    return this._value()
  }

  // Call both the map and join methods
  flatMap (fn) {
    return this.map(fn).join()
  }
}

let readFile = (filename) = > {
  return new IO(() = > {
    return fs.readFileSync(filename, 'utf-8')})}let print = (x) = > {
  return new IO(() = > {
    console.log(x)
    return x
  })
}

let r = readFile('package.json')
          .flatMap(print)
          .join()     
// Order of execution
/** * readFile reads the file and returns an IO functor * this is called with the IO functor returned by readFile * and passes in a print function argument * This is called with the flatMap function. The current print is merged with this._value, which returns a new functor * (this._value is what readFile returns on the IO functor: * () => {return fs.readfilesync (filename, 'utf-8')} *) * This._value is the combination of print and this._value. Print returns the IO functor * */ from print
 
 r = readFile('package.json')
        // Use map to process data directly after reading the file
        .map(fp.toUpper)
        .flatMap(print)
        .join()  

// After reading the file, I want to process the data.
// Just call the map method after reading the file

/ * * * {" NAME ":" FUNCTOR ", "VERSION" : "1.0.0", "DESCRIPTION" : ""," MAIN ":" EITHER. JS ", "SCRIPTS" : {" TEST ": "ECHO \"ERROR: NO TEST SPECIFIED\" && EXIT 1" }, "KEYWORDS": [], "AUTHOR": "", "LICENSE": "ISC", "DEPENDENCIES": {" FOLKTALE ":" ^ "2.3.2," LODASH ":" ^ 4.17.20 "}} * /
Copy the code

Summary of Monad functors

What is Monad?

Functors with static IO methods and join methods

When to use Monad?
  • When a function returns a functor, we need to think about Monad. Monad can help us solve the problem of functor nesting.
  • We call the map method when we want to return a function that returns a value
  • The flatMap method is used when we want to merge a function that returns a functor

The appendix

  • Functional programming is north
  • Introduction to functional programming
  • Pointfree programming Style Guide
  • The illustration Monad
  • Functors, Applicatives, And Monads In Pictures

History article portal

  • Big front-end attack road (two)JS asynchronous programming
  • The Road to big Front – end Attack

reference

Pull hook big front end training camp