Functional programming concepts

What functional programming?

Functional Programming (FP) is one of the Programming paradigms. We often hear of procedural Programming and object-oriented Programming.

Object Oriented Programming (OOP) :

Things in the real world are abstracted into classes and objects in the program world, and the relationship between things and events is demonstrated through encapsulation, inheritance and polymorphism

Advantages: easy to maintain, easy to reuse, easy to expand, because of object-oriented encapsulation, inheritance, polymorphism, can design a low coupling system, make the system more flexible, more easy to maintain

Disadvantages: Lower performance than process-oriented

Procedure Oriented Programming (POP) :

Analyze the steps needed to solve the problem, and then use the function to implement these steps step by step, when using one call in turn can be advantages: performance is higher than object-oriented, because the class call needs to be instantiated, the cost is relatively large, more consumption of resources; For example, SCM, embedded development, Linux/Unix and so on are generally process-oriented development, performance is the most important factor.

Disadvantages: No object oriented easy to maintain, easy to reuse, easy to extend

Functional Programming (FP) :

The function in functional programming refers not to the function in the program (method), but to the function in mathematics, namely the mapping relationship. Functional programming describes the mapping between data (functions)

Advantages:

  1. Better state management: Because the idea is that stateless, or less state, minimizes these unknowns, optimizes code, and reduces error cases
  2. Simpler reuse: fixed input -> fixed output, no other external variables, and no side effects. In this way, when code is reused, there is no need to consider its internal implementation and external effects
  3. More elegant combinations: At a large scale, a web page is made up of components. At a smaller level, a function may be composed of several smaller functions. Greater reuse leads to greater composability
  4. Implicit benefits. Reduce code and improve maintainability

Cons: Poor performance, heavy use of closures

Basic functional programming

Why is a function a first class citizen

Functions can be treated like normal objects (they can also do what variables can do) :

  1. Functions can be stored in variables
  2. Functions can be taken as arguments
  3. Functions can be returned as values

Higher-order functions are defined as follows: 1. They can be passed as arguments; 2. They can be returned as values

Closures (key)

Definition: A closure is a mechanism to save or protect data, and it is this that increases memory overhead. This mechanism arises: a closure occurs when a function can remember and access the scope in which it is located. Even if the function is executed outside the current lexical scope.

Colloquial: you can call the inner function of a function from another scope and access members of that function’s scope

Example 1:

    function foo(){
        let a = 0
        function bar(){
            console.log(a)
        }
        return bar
    }
    var fn = foo()
    fn() //0 This is the closure effect
Copy the code

It’s easy to see that the scope of bar() accesses the inner scope of foo(), and we treat the function object referenced by bar itself as the return value. When foo() is executed, foo’s internal function bar is assigned to the variable to fn and called for execution. Executing fn() is equivalent to executing bar(), that is, it is executing outside the scope of its own lexical definition.

Usually after the foo function is executed, the entire internal scope is destroyed: the engine’s garbage collector frees up memory that is not in use. But because of closures, garbage collection is prevented, so that foo’s inner scope is still alive, that bar() itself is in use, and that foo’s inner scope is always alive to be referenced by bar() at any later time.

Example 2: The function is executed only once

//once
function once (fn) {
    let done = false 
    console.log(done) // Done will be false after the first execution and will be retained
    return function () {
       if(! done) { done =true 
          return fn.apply(this.arguments)}}}let pay = once(function(a){
    console.log('Paid' + a + 'RMB')
})

pay(5) // 5RMB is paid
pay(6) // None (no execution)
pay(7) // None (no execution)

// The first time pay is executed, the done value is set to true and is retained. Every subsequent pay is not returned because the done value is false
Copy the code

The nature of closures: functions are placed on an execution stack when they are executed and removed from the stack when they are finished, but scope members on the heap cannot be freed by external references, so internal functions can still access members of external functions

Pure function – the same input has the same output

Concept: The same input always yields the same output, without any observable side effect (influencing internal results by externally changing variables) advantage:

  1. Cacheable result
  2. Easier to test
  3. In parallel processing, there may be unexpected situations when parallel operation of shared memory data in multi-threaded environment. Pure functions do not need to access shared memory data, so pure functions can be run arbitrarily in parallel environment.

Example 1: Cache the result function

function memorize(fn){
    let cache = {}
    return function(){
        let arg_str = JSON.stringfy(arguments)
        cache[arg_str] = cache[arg_str] || fn.apply(fn,argumens)
        return cache[arg_str]
    }
}
Copy the code

The side effect is that a function becomes impure. A pure function returns the same output from the same input. If the function is dependent on an external state, the output cannot be guaranteed to be the same. Source of side effects:

  1. The configuration file
  2. The database
  3. Getting user interaction
  4. .

All external interactions may bring side effects, which also reduce the universality of methods and make them unsuitable for extension and reusability. At the same time, side effects will bring security risks and uncertainty to programs, but side effects cannot be completely prohibited, and they should be controlled as far as possible.

Example 2:

/ / not pure
let mini = 18
function checkAge (age) {
    return age >= mini
}
// Pure (hardcoded, can be curified later)
function checkAge (age) {
    let mini = 18
    return age >= mini
}
Copy the code

Curryization – Simplification of functions of several variables

Concept: When a function has multiple arguments, it is called with some arguments passed (which never change), and then a new function takes the rest of the arguments and returns the result.

Example 1:

Log (add(1)(2)(3)) // 6; console.log(add(1, 2, 3)(4)) // 10; console.log(add(1)(2)(3)(4)(5)) // 15;Copy the code
function add(. args){
    if(! args.length)return
    // The first call
    let total = args.reduce((prev,cur) = >{
            return prev + cur
    },0)
    // Call later because the closure total is always saved
    function sum(. nextArgs){
        let all = nextArgs.reduce((prev,cur) = >{
                return prev + cur
        },0)

        total += all
        return sum
    }
    // log a reference to a function uses the function.tostring () to get the string definition of the function
    sum.toString = () = > total
    / / total output
    sum.valueOf = () = > total
    // console.log(total)
    return sum
}

console.log(add(1.2.3)) / / 6
let a = add(2.2.3) 
console.log(a) / / 7
Copy the code

Example 2: Handwritten currying implementation function

    function curry(fn){
        return function curried(. args){
            // The argument is compared with the parameter
            if(args.length < fn.length){
                // Return the function with the remaining arguments
                return function(. restArgs){
                    returncurried(... args.concat(... restArgs)) } }returnfn(... args) } }Copy the code

Combination of functions

Concept: If a function has to go through multiple processes to get its final value, it is possible to combine the intermediate functions into a single function, which is executed from right to left by default

Example 1: Use the flowRight() function in lodash

    // Introduce the lodash function
    const_=require('lodash')
    // The string is programmed with uppercase
    consttoUpper=s= >s.toUpperCase()
    // Invert the array
    constreverse=arr= >arr.reverse()
    // Take the first digit
    constfirst=arr= >arr[0]
    // Turn the array invert --> turn the first element into uppercase --> turn the first element into uppercase
    constf=_.flowRight(toUpper, first, reverse)
    console.log(f(['one'.'two'.'three'])) //THREE
Copy the code

Example 2: Handwriting composition function compose

function compose(. funs){
    return function(value){
        funs.reverse().reduce(function(acc,fn) = >{
            return(fn(acc))
        },value)
    }
}

/ / ES6 version
const compose = (. funs) = > value= > funs.reverse().reduce((acc,fn) = > fn(acc),value) 
Copy the code

Compose (a,b,c),compose(a,b,c),compose(a,b,c),compose(a,b,c),compose(a,b,c),compose(a,b,c),compose(a,b,c),compose(a,b),c)

PointFree – a programming style

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

  1. You do not need to specify the data being processed
  2. You just have to synthesize it
  3. Some auxiliary basic operations need to be defined

Example:

Now find all the female users and return the phone numbers of the female users

// Data format
var users = [
  {name:"john lennon".sex: "male".phone:"123"},
  {name:"prince".sex: "male".phone:"234"},
  {name:"rihanna".sex:"female".phone:"345"},
  {name:"taylor swift".sex:"female".phone:"456"}]// The usual solution is to write a getFemalPhone function
function getFemalPhone(users){
    var phones = []
    for(let item of users){
        if(item.sex === 'female'){
            phones.push(item.phone)
        }
    }
    return phones
}
/ / call
getFemalPhone(users)

// The PointFree style breaks down the requirements into two helper functions: getFemal() to query a woman and getPhone() to get a number
getFemal(users){
    return users.filter(u= > u.sex === 'female')}getPhone(users){
    retrun users.map(u= > u.phone)
}
/ / define
const getFemalPhone = compose(getPhone,getFemal)
/ / call
getFemalPhone(users)
Copy the code

Bottom line: The benefit of using pointFree is that when you have a need to get a male number, or get all numbers, you don’t need to rewrite a new function but use a combination of helper functions. And adhere to the single responsibility principle.

Functor

Functor: is a special container that contains values and their morphing relationships (this morphing relationship is a function). Functor: is a special container that contains values and their morphing relationships (this morphing relationship is a function). Functor: is a special container that contains values and their morphing relationships (this morphing relationship is a function). In functional programming to control side effects in a controllable range, exception handling, asynchronous operations, etc.

Example 1. Write a primitive functor

    // A container
    class Container{
    // Of static method, you can omit the new keyword to create the object
        static of(value){
            return new Container(value)
        }
        // The constructor
        constructor(value){
         this._value = value
        }
        // The map method, passing in the deformation relation, maps each value in a container to another container
        map(fn){
            return Container.of(fn(this._value))
        }
    }
    / / test
    let functor = Container.of(3).map(x= > x + 1).map(x= > x * x)
    console.log(functor) Container {_value: 36} The result is a functor
Copy the code

Conclusion:

  1. Numerical programming does not operate directly on values, but is done by functors
  2. A functor is an object that implements the Map contract

3 We can think of a functor as a box that encloses a value of 4. To process the value in the box, we need to pass a function (pure function) to the box’s map method to process the value. 6. Finally, the map method returns a box (functor) containing the new value.

MayBe functor – external null case to handle (control side effects within allowable range)

class MayBe {
    static of (value) {
        return new Container(value)
    }
    constructor (value) {
        this._value = value
    }
    map(fn) {
        return thhis.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value))
    }
    isNothing () {
        return this._value === null || this._value === undefined}}// Pass a null value
MayBe.of(null).map(x= > x.toUpperCase()) // Return MayBe {_value:null}
Copy the code

Note: It is currently difficult to identify which step caused the null value problem

Either functor – one of two (exceptions make functions impure, and Either functor can be used for exception handling)

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))
    }
}
/ / use
function parseJSON(json) {
    try {
        return Right.of(JSON.parse(json));
    } catch (e) {
        return Left.of({ error: e.message}); }}let r = parseJSON('{ "name": "zs" }')
.map(x= > x.name.toUpperCase())
console.log(r)
Copy the code

IO functor

  1. The _value in the IO functor is a function, and the function is treated as a value here
  2. The IO functor can store an impure action in _value, delay execution of the impure operation (lazy execution), and wrap the current operation pure
  3. Leave impure operations to the caller
const fp = require('lodash/fp')
class IO {
    static of (x) {
        return new IO(function () {
            return x
        })
    }
    constructor (fn) {
        this._value = fn
    }
    map (fn) {
        // Combine the current value and the passed fn into a new function
        return new IO(fp.flowRight(fn, this._value))
    }
}

/ / call
let io = IO.of(process).map(p= > p.execPath)
console.log(io._value()) //value is a function
Copy the code

Example 1: Asynchronous reading of a file using the Task functor

The functor is in the Folktale library (a standard functional programming library)
const { task } = require('folktale/concurrency/task') / / version 2.3.2
function readFile(filename){
    return task(resolver= >{
        fs.readFile(filename,'utf-8'.(err,data) = >{
            if(err) resolver.reject(err)
            resolver.resolve(data)
        })
    })
}
// Call run to execute
readFile('test.json')
    .map(split('\n'))
    .map(find(x= > x.includes('version')))
    .run().listen({
     onRejected:err= >{
        console.log(err)
     },
     onResolved:value= >{
        console.log(value)
     }
    })
Copy the code

Margin functor – Functor that implements the of static method

Note: The “of” method is used to avoid using new to create objects. The deeper meaning is that the “of” method is used to put the value into the Context (put the value into the container, use the map to process the value).

class Container {
    static of (value) {
    return newThe Container (value)}... }Copy the code

Monad

  1. Monad functors are flat margin functors, IO(IO(x))
  2. A functor is a Monad if it has join and of methods and obeies some laws
const fp = require('lodash/fp')
const fs = require('fs')
let readFile = function (filename) {
    return new IO(function() {
    return fs.readFileSync(filename, 'utf-8')})}let print = function(x) {
    return new IO(function() {
        console.log(x)
        return x
    })
}
// Both of these use IO functors
// IO(IO(x))
let cat = fp.flowRight(print, readFile)
/ / call
let r = cat('package.json')._value()._value() // There is value nesting
console.log(r)
// IO Monad 
class IO {
    static of (x) {
        return new IO(function () {
            return x
        })
    }
    constructor (fn) {
        this._value = fn
    }
    map (fn) {
        return new IO(fp.flowRight(fn, this._value))
    }
    join () {
        return this._value()
    }
    flatMap (fn) {
        return this.map(fn).join()
    }
}
let r = readFile('package.json')
.map(fp.toUpper)
.flatMap(print)
.join()
Copy the code

conclusion

Functional programming as a programming paradigm, with the use of React and Vue3 becoming more and more attention, this can be abandoned in functional programming. In the packaging process, Tree Shaking can be better used to filter useless code and facilitate testing and parallel processing. It has a good application scenario in the future. Functional development libraries: Odash, underscore, Ramda