What is functional programming

Functional Programming (FP), FP is one of the Programming paradigms, we often hear of procedural Programming, object-oriented Programming

  • 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: Abstracting things and their relationships from the real world to the program world
    • 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 (pure function)
    • Functional programming is used to describe mappings between data (functions)
Let num = 1 let num2 = 2 console.log(num + num2) // 3 eg: Function sum(x, y) {return x + y} let result = sum(num, num2) console.log(result) // 3Copy the code

The function is a first-class citizen

  • Functions can be stored in variables
  • Function as argument
  • Function as the return value

In JavaScript, a Function is just an ordinary object (via new Function ()), we can store the Function in a variable/array, it can also be a variable and return value of another Function, We can even construct a new Function while the program is running with new Function (‘alert(1)’)

  • Assign a function to a variable
Let fn = function () {console.log('Hello World')} fn() const BlogController = {index (posts): Return views.index(posts), show(post) : return views.show(post)} // Optimize const BlogController = {index: views.index, show: views.show }Copy the code
  • The function is first-class citizen, which is the basis of higher order function and Currization

Higher-order functions

What is a higher-order function

  • Higher-order functions
    • You can pass a function as an argument to another function
    • You can treat a function as the return result of another function
  • Function as argument
// forEach
function (arr, fn) {
    for(let i = 0; i < arr.length; i ++) {
        fn(arr[i])
    }
}
// filter
function (arr, fn) {
    const result = []
    for (let i = 0; i < arr.length; i ++) {
        if (fn(arr[i])) {
            result.push(arr[i])
        }
    }
    return result
}
Copy the code
  • Function as the return value
function makeFn () { const msg = 'Hello Function' return function () { console.log(msg) } } const fn = makeFn() fn() // Hello Function // once function once (fn) { let flag = false return function () { if (! flag) { flag = true fn.apply(this, Arguments)}}} const pay = once(function (money) {console.log(' paid successfully ${money} yuan ')}) // pay(5) pay(5) pay(5) pay(5) pay(5)Copy the code

The meaning of using higher-order functions

  • Abstraction can help us mask the details and focus only on our goals
  • Higher-order functions are used to abstract generic problems
Const arr = [1, 2, 3, 4] for(let I = 0; i < arr.length; I ++) {console.log(arr[I])} const arr = [1, 2, 3, 4] item => { console.log(item) }) let r = filter(arr, item => { return item % 2 === 0 })Copy the code

Commonly used higher-order functions

  • forEach
  • map
  • filter
  • every
  • some
  • find/findIndex
  • reduce
  • sort
  • .
const map = (arr, fn) => { const result = [] for(value of arr) { result.push(fn(value)) } return result } const every = (arr, fn) => { let result = false for(value of arr) { result = fn(value) if (! result) { break } } return result } const some = (arr, fn) => { let result = false for(value of arr) { result = fn(value) if (result) break } return result }Copy the code

closure

  • 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 from another scope and access members of that function’s scope
function makeFn () { const msg = 'Hello Function' return function () { console.log(msg) } } const fn = makeFn() fn() // Hello Function // once function once (fn) { let flag = false return function () { if (! flag) { flag = true fn.apply(this, Arguments)}}} const pay = once(function (money) {console.log(' paid successfully ${money} yuan ')}) // pay(5) pay(5) pay(5) pay(5) pay(5)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
  • Closure case
Function makePower (power) {return function (x) {return math.pow (x, power) } } const power2 = makePower(2) const power3 = makePower(3) console.log(power2(4)) console.log(power3(4)) // The first number is the base salary, Function makeSalary(x) {return function (y) {return x + y}} let salaryLevel1 = makeSalary(1500) let salaryLevel2 = makeSalary(2500) console.log(salaryLevel1(2000)) console.log(salaryLevel1(3000))Copy the code

Pure functions

Pure function concept

  • Pure functions: The same input always yields the same outputWithout any observable side effects
    • A pure function is like a function in mathematics, 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 Numbers = [1, 2, 3, 4, 5] / / Numbers of meromorphic functions. The slice (0, 3) / / = > [1, 2, 3] Numbers. The slice (0, 3) / / = > [1, 2, 3] Numbers. Slice (0, 3) / / = > [1, 2, 3] / / not pure function Numbers. The splice (0, 3) / / = > [1, 2, 3] Numbers. The splice (0, 3) / / = > [4, 5] numbers.splice(0, 3) // => []Copy the code
  • Functional programming does not preserve the middle of the calculation, so variables are immutable (stateless)
  • We can hand off the results of one function to another

The benefits of pure functions

  • cacheable
    • Because pure functions always have the same result on the same input, the result of a pure function can be cached
const _ = require('lodash')
function getArea (r) {
    return Math.PI * r * r
}
let getAreaWithMemory = _.memoize(getArea)
console.log(getAreaWithMemory(4))
Copy the code
  • Memorize a memorize function for yourself
    function memorize (fn) {
        let cache = {}
        return function () {
            let arg_str = JSON.stringify(arguments)
            cache[arg_str] = cache[arg_str] || fn.apply(fn, arguments)
            return cache[arg_str]
        }
    }
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
Function checkAge (age) {return age >= mini} Function checkAge (age) {let mini = 18 return age >= mini} function checkAge (age) {let mini = 18 return age >= mini}Copy the code

Side effects make a function impure (as in the example above). Pure functions return the same output based on the same input, and can have side effects if the function depends on external state and cannot guarantee the same output.

Source of side effects

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

All external interactions may bring side effects, which also reduce the universality of the method and make it unsuitable for expansion and reuse. At the same time, side effects will bring security risks to the program and bring uncertainty to the program. However, side effects cannot be completely banned, and they should be controlled as much as possible

Haskell Brooks Curry

  • Use Coriation to solve the hard-coded problem in the previous case
Function checkAge (age) {let mini = 18 return age >= mini} age) { return age >= min } checkAge(10, 18) checkAge(10, 20) checkAge(20, Function (min) {return age >= min}} const checkAge = min => age => age >= min let checkAge18 = checkAge(18) let checkAge20 = checkAge(20) checkAge18(20) checkAge20(20)Copy the code
  • Currie (Currying)
    • When a function has more than one argument, it is called by passing some of the arguments (these arguments never change).
    • It then returns a new function that takes the remaining arguments and returns the result

The Currization function in Lodash

  • _.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') // function getSum (a, b, C) {return a + b + c} // Let curried = _. Curry (getSum) // Test curried(1, 2, 3) curried(1)(2)(3) curried(1, 2) (3)Copy the code
  • case
const _ = require('lodash')
const match = _.curry(function (reg, str) { 
    return str.match(reg)
})
const haveSpace = match(/\s+/g)
const haveNumber = match(/\d+/g)
console.log(haveSpace('hello world'))
console.log(haveNumber('25$'))
const filter = _.curry(function (func, array) { 
    return array.filter(func)
})
console.log(filter(haveSpace, ['John Connor', 'John_Donne']))
const findSpace = filter(haveSpace)
console.log(findSpace(['John Connor', 'John_Donne']))
Copy the code
  • Simulate the implementation of _.curry()
function curry (func) { return function curriedFn (... If (args. Length < func.length) {return function () {return curriedFn(... Args. concat(array. from(arguments)))}} // Return func(... args) } }Copy the code

conclusion

  • Corrification allows us to pass in fewer arguments to a function and get a new function that has memorized some of the fixed arguments
  • This is a kind of ‘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

  • Pure functions and Currization are easy to write onion code h(g(f(x)).
    • Take the last element of the array and convert it to uppercase,.toUpper(.first(_.reverse(array)))

  • Function composition allows us to recombine fine-grained functions to generate a new function

The pipe

fn = compose(f1, f2, f3)
b = fn(a)
Copy the code

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
// function compose (f, g) { return function (x) { return f(g(x)) } } function first (arr) { return arr[0] } function reverse (arr) { return arr.reverse() } let last = compose(first, reverse) console.log(last([1, 2, 3, 4]))Copy the code
  • Function composition 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
const _ = require('lodash')
const toUpper = s => s.toUpperCase()
const reverse = arr => arr.reverse()
const first = arr => arr[0]
const f = _.flowRight(toUpper, first, reverse)
console.log(f(['one', 'two', 'three']))
Copy the code
  • Simulate the flowRight method for implementing LoDash
// multifunction compose function compose (... fns) { return function (value) { return fns.revers().reduce(function (acc, fn) { return fn(acc) }, value) } } // ES6 const compose = (... fns) => value => fns.revers().reduce((acc, fn) => fn(acc), value)Copy the code
  • The combination of functions must satisfy associativity:
    • We can either combine g with H, or f with g, and it’s the same thing
Let associative = compose(f, g, h) = compose(f, g, g) = compose(f, g, g, g) h)) // trueCopy the code
  • So the code could also look like this
const _ = require('lodash')

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

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

debugging

  • How do I debug composite functions
eg: const f = _.flowRight(_.toUpper, _.first, _.reverse) console.log(f(['one', 'two', 'three'])) eg2: const _ = require('lodash') const trace = _.curry((tag, v) => { console.log(tag, v) return v }) const split = _.curry((sep, str) => _.split(str, sep)) const join = _.curry((sep, array) => _.join(array, sep)) const map = _.curry((fn, array) => _.map(array, fn)) const f = _.flowRight(join('-'), Trace (' after map '), map(_.tolower), trace(' after split '), split(' ')) console.log(f('NEVER SAY DIE'))Copy the code
  • lodash/fp
    • Lodash’s FP module provides a practical approach to functional programming friendly
    • Immutable auto-curried iteratee-first data-last method is provided
eg: / / lodash module const _ = the require (' lodash) _. The map ([' a ', 'b', 'c'], _ the toUpper) / / = > [' a ', 'b', 'c'] _. The map ([' a ', 'b', 'c']) // => ['a', 'b', 'c'] _.split('Hello World', "') / / lodash/fp modules const fp = the require (' lodash/fp) fp. The map (fp) toUpper, [' a ', 'b', 'c']) fp. The map (fp) toUpper) ([' a ', 'b', 'c']) fp.split(' ', 'Hello World') fp.split(' ')('Hello World') eg2: const fp = require('lodash/fp') const f = fp.flowRight(fp.join('-'), fp.map(_.toLower), fp.split(' ')) console.log(f('NEVER SAY DIE'))Copy the code

Point Free

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
const f = fp.flowRight(fp.join('-'), fp.map(_.toLower), fp.split(' '))
Copy the code
  • Case presentation
// Non-point Free mode // Hello World => hello_world function f (word) {return word.tolowerCase ().replace(/\s+/g, '_'); } // Point Free const fp = require('lodash/fp') const f = fp.flowRight(fp.replace(/\s+/g, '_'), fp.toLower) console.log(f('Hello World'))Copy the code
  • Using the Point Free pattern, extract and capitalize the first letter of the word
const fp = require('lodash/fp')
const firstLetterToUpper = fp.flowRight(join('. '), fp.map(fp.flowRight(fp.first, fp.toUpper)), split(' '))
console.log(firstLetterToUpper('world wild web'))
// => W. W. W
Copy the code

Functor ==== we will learn it later. We haven’t seen it at work for the time being

Why do we learn functors

So far we’ve covered some of the basics of functional programming, but we haven’t shown you how to keep side effects under control, exception handling, asynchronous operations, and so on in functional programming.

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)

Functor Functor

Class Container {// of static method, Static of (value) {return new Container(value)} constructor (value) {this._value = value} // map Method, afferent deformation relation, Map each value in the Container to another Container map(fn) {return container.of (fn(this._value))}} // Test container.of (3).map(x => x + 2).map(x => x *  x)Copy the code