What is Functional Programming?

Functional programming is a programming paradigm, it will be considered as functional operation of computing, and avoid using program state variable and object in functional programming, the function is the first kind of object, meaning that a function, both can be used as input parameters of other functions, can also return a value from a function, has been modified or assigned to a variable.

Functions in functional programming are not functions (methods) in programs, but mappings in mathematics, such as y = sin(x), the relationship between x and y

Higher-order functions

Let’s take a look at wikipedia’s definition of higher-order functions:

In mathematics and computer science, a higher-order function is one that satisfies at least one of the following conditions:

  • Takes one or more functions as input
  • Output a function

In simple terms, when we define a function that takes a function or returns a function, the function is a higher-order function. For JavaScript, both are true.

Function as argument

    function callbackFn() {
        console.log('Higher order function')}function fnParam(fn) {
        console.log('Ordinary function')
        // the fn function is passed as an argument
        fn()
    }
    / / test
    fnParam(callbackFn)
    
Copy the code

In the example above, the fn function is passed as an argument and is called a callback function

Callbacks are the cornerstone of asynchrony. In the example above, callbackFn is passed as an argument to the fnParam function. The execution of callbackFn is determined by fnParam.

Many of these functions are built into the JavaScript language as arguments to higher-order functions, such as

  • Array.prototype.map
  • Array.prototype.filter
  • Array.prototype.find
  • .

Take array.prototype. map as an example to explain in detail

Use higher-order functions

      // map Example
     const arr = [1.2.3.4]
     const result = arr.map(item= > item * 2)
     console.log(arr)  // [1, 2, 3, 4]
     console.log(result)  // // [2, 4, 6, 8]
Copy the code

Higher-order functions are not used

     // Synchronize code to implement map function
     const arr = [1.2.3.4]
     const result = []
     for(let i = 0; i < arr.length; i++) {
         const item = arr[i]
         result.push(item * 2)}console.log(arr)  // [1, 2, 3, 4]
     console.log(result)  // // [2, 4, 6, 8]
Copy the code

Analog implementation

    / / map
    const map = (arr,fn) = >{
        let res = [];
        for(let i = 0; i < arr.length; i++){
            res.push(fn(i))
        }
        return res;
    }
    / / test
    const newAry = map([1.23.4.5.6].item= >item**2)
    console.log(newAry) // [1, 529, 16, 25, 36]
Copy the code
    / / analog filter
    function filter(arr,fn){
        let res = [];
        for(let i = 0; i < arr.length; i++){
            if(fn(arr[i])){
                res.push(arr[i])
            }
        }
        return res
    }
    / / test
    const ary = filter([1.2.34.5.6].function(item){
        return item%2= = =0;
    })
    console.log(ary) / / [6] 2, 34,
Copy the code

Function as the return value

Let’s look at a simple example

    function getMsg() {
        const msg = 'hello msg'
        // function as return value
        return function() {
            console.log('msg')}}/ / test
    const firstAction = getMsg()
    firstAction() // hello msg
Copy the code

The result of getMsg is a function assigned to firstAction, which is the function returned. There’s going to be a closure

Next we take advantage of these two features of higher-order functions to implement a function that can only be called once

    function once(fn) {
        let done = false
        debugger
        return function() {
            if(! done) { done =true
                return fn.apply(this.arguments)}}}const pay = once(function(money) {
        console.log('Paid'+ money)
    })
    pay(5)
    pay(5)
    pay(5)
Copy the code

View closures in the browser environment

Closures can call the inner function of a function in another scope and access members of that function’s scope (that is, extend scope)

Closure nature: Functions are placed on an execution stack at execution time, and are removed from the stack when the function completes execution. However, scoped members on the heap cannot be freed because they are referenced externally, so internal functions can still access external function members and are placed on an execution stack at execution time

Currie,

Call a function that takes only part of its arguments (those arguments never change), and then return a new function that takes the rest of the arguments and returns the result

  // A simple example of currization
  function checkAge(min) {
    return function(age) {
      return age >= min
    }
  }
  // Only partial parameters are passed
  const age18 = checkAge(18);
  // Accept the remaining parameters
  console.log(age18(22));
  console.log(age18(55));
  console.log(age18(17));
Copy the code

Simulate currification in LoDash (key point: execute function only if argument is equal to the number of parameters, otherwise cache parameters)

 function curry(fn) {
   return function curriedFn(. args) {
     // Determine the number of arguments and parameters
     // function. length The length property specifies the number of parameters of the Function
     if (args.length < fn.length) {
       return function() {
         // Cache parameterscurriedFn(... args.concat(Array.from(arguments)))}}returnfn(... args) } }/ / test
 function getSum(a, b, c) {
   rerturn a + b + c
 }

 const curried = curry(getSum)
 console.log(curryFun(1.2.3));
 console.log(curryFun(1.2) (3));
 console.log(curryFun(1) (2.3));
Copy the code

Function composition

If a function has to go through several functions to get the final value, then you can combine the intermediate functions into a single function

Functions are conduits of data, connecting multiple conduits to allow data to pass through to form the final result

Function combinations are executed from right to left by default, with each function taking one argument and returning the corresponding result

  // A function that implements a function combination function
  function compose(. args) {
    return function(value) {
      // First invert the array, then execute the functions in turn through reduce
      return args.reverse().reduce(function(acc, fn) {
        return fn(acc)
      }, value)
    }
  }
  // arrow function
  const compose_arrow = (. args) = > value= > args.reverse().reduce((acc, fn) = > fn(acc), value)

  / / test
  const reverse = (arr) = >arr.reverse();
  const first = (arr) = >arr[0];
  const test1 = compose(first,reverse)
  const test2 = compose_arrow(first,reverse)
  console.log(test1([1.5.6.4.8.6.'dsadasdsd']));
  console.log(test2([1.5.6.4.8.6.'dsadasdsd']));
Copy the code

Pure functions

The same input gets the same output without any observable side effects

Pure function example

  const array = [1.2.3.4.5]
  console.log(array.slice(0.3))
  console.log(array.slice(0.3))
  console.log(array.slice(0.3))
  // Execute three times to get the same result
Copy the code

Example of impure functions

  const array = [1.2.3.4.5]
  console.log(array.splice(0.3))
  console.log(array.splice(0.3))
  console.log(array.splice(0.3))
  // Execute three times with different results
Copy the code

Benefits of pure functions

  • Cacheable: Because a pure function always has the same output for the same input, its results can be cached using closures (memory function)

Implement a memory function

  function getArea (r) {
    console.log(r);
    return Math.PI * r * r
  }
  function memoize(fn) {
    // Memory objects
    const cache = {}
    // Closure functions
    return function() {
      const key = JSON.stringify(arguments)
      // If there is a cache, fetch the cache directly; if there is no cache, recalculate.
      cache[key] = cache[key] || fn.apply(fn, arguments)
      return cache[key]
    }
  }
  / / test
  let memoizeFun = memoize(getArea)
  console.log(memoizeFun(4));
  console.log(memoizeFun(4));
Copy the code
  • Testable: Pure functions make testing easier (because pure functions always have inputs and outputs, and unit tests assert that result
  • Parallel processing (Web Worker): Operating on shared memory data in parallel in a multi-threaded 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

Side effects

Examples of side effects

  / / not pure
  let mini = 18
  function checkAge(age) {
    // Depending on the external variable mini, when mini changes, the same input does not necessarily get the same output
    return age >= mini
  }
Copy the code

If the function depends on the external state and cannot guarantee the same output, then you have a side effect, which makes the function impure

Side effects: Configuration files, databases, obtaining user input… (All interactions with the outside world have potential side effects)

Side effects can not be completely banned, as much as possible to control their occurrence within a controllable range

Functor (Functor)

Container: contains values and worth distortion relations (this distortion relation is the function)

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 {
    // Encapsulates the Container
    static of(value) {
      return new Container(value);
    }
    constructor(value) {
      this._value = value;
    }
    // Contract object
    map(fn) {
    /** * fn is a callback function (pure function) * that takes the value handled by fn as an argument to instantiate Container */
      return Container.of(fn(this._value)); }}let result = Container.of(5).map((x) = > x + 2).map((x) = > x * x);
  console.log(result);
Copy the code

Error: Invalid value input value cannot be judged, resulting in program termination (MapBe functor)

MayBe functor

Function: Handles external null cases (null side effects are allowed)

  / / MayBe functor
  class MayBe {
    static of(value) {
      return new MayBe(value);
    }
    constructor(value) {
      this._value = value;
    }
    map(fn) {
      return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value));
    }
    isNothing() {
      return this._value === null || this._value === undefined; }}let result = MayBe.of(null).map((x) = > x.toUpperCase());
  console.log(result);
Copy the code

The nullMayBe functor can’t accurately locate the number of steps assigned to null when there are too many chain calls (Either functor)

Either functor

Either of the two, similar to if… else… The processing of

Exceptions make functions impure, and Either functors can be used for exception handling

 / / Either functor
  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)); }}// test
  function parseJSON(str) {
    try {
      return Right.of(JSON.parse(str));
    } catch (e) {
      // Error message saved
      return Left.of({ error: e.message }); }}let result3 = parseJSON('{name: zs}');
  let result4 = parseJSON('{ "name": "zs" }');
  console.log(result3);
  console.log(result4);
Copy the code

IO functor

The _value in the IO functor is a function, and we’re treating the function as a value

IO functors can store impure actions in _value, delay execution of the impure operation (lazy execution), and wrap the current operation pure

Leave impure operations to the caller

  // Function combination
  function compose(. args) {
    return function(value) {
      // First invert the array, then execute the functions in turn through reduce
      return args.reverse().reduce(function(acc, fn) {
        return fn(acc);
      }, value);
    };
  }

  / / IO functor
  class IO {
    static of(value) {
      return new IO(function() {
        return value;
      });
    }
    constructor(fn) {
      this._value = fn;
    }
    map(fn) {
      // Combine the current value and the incoming fn into a new function
      return new IO(compose(fn, this._value)); }}// test
  const result = IO.of(process).map((p) = > p.execPath);
  console.log(result._value());
Copy the code

Problem:.value().value() is not nice to write when calling nested functors to execute results (Monad functors solve this problem)

Monad functor

Monad functor is a linear functor that can be flattened. Monad mainly solves the problem of nested output of IO(IO(x)) functors.

A functor is a Monad if it has both join and of methods and obeies some laws

If function nesting can be combined with functions, functor nesting can be Monad

  // Function combination
  function compose(. args) {
    return function(value) {
      // First invert the array, then execute the functions in turn through reduce
      return args.reverse().reduce(function(acc, fn) {
        return fn(acc);
      }, value);
    };
  }
  
class Monad {
    static of (value) { 
      	The //of method still receives a value, but it encapsulates the value in the function
        return new Monad(function(){ 
            return value
        })
    }
    constructor(fn){
      	// Accept a function to minimize side effects
        this.value = fn 
    }
    map(fn){//map is an operation that processes values
      	// The output is always a function stored in value
        return new Monad(compose(fn,this.value)) 
    }
    join(){
      	// This is the flatMap operation
        return this.value();
    }
    flatMap(fn){
      	//flatMap handles nested functors to avoid repeating the.value() operation
        return this.map(fn).join(); }}Copy the code

Functional programming library

Folktale: folktale.origamitower.com/

Folktale is a standard functional programming library that differs from Lodash and Ramada in that it does not provide many functions. It only provides functions for composing,curry, etc., and functors :Task(asynchronous operation),Either,MayBe, etc.

Folktale Task asynchronous operations

// The task implementation is too complex to emulate
// Folktale 2.x is quite different from tasks in 1.x, and 1.0 is closer to the functors we are demonstrating now
// Use 2.3.2 to illustrate
const {task} = require('folktale/concurrency/task');
const _ = require('lodash/fp');
const fs = require('fs');
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()
.listen({
    onRejected(err){
        console.log(err);
    },
    onResolved(data){
        console.log('resolution version',data); }})Copy the code