This series of articles will introduce functional programming, examine functional programming libraries, and use functional programming in React

Imperative and declarative programming

Take the example of making tea to distinguish imperative programming from declarative programming

  • Imperative programming

1. Boil the water (in the first person) 2. Get a teacup 3

  • Declarative programming

1. Make me a cup of tea

For the demo

// Imperative programming
const convert = function(arr) {
  const result = []
  for (let i = 0; i < arr.length; i++) {
    result[i] = arr[i].toLowerCase()
  }
  return result
}

// Declarative programming
const convert = function(arr) {
  return arr.map(r= > r.toLowerCase())
}
Copy the code

What is functional programming

Functional programming is the paradigm of declarative programming. In functional programming, data is passed through pipes of pure functions.

Functional programming can help simplify code implementation by using simple mathematical methods such as commutative, associative, and distributive laws.

It has the following features:

  • Purity: Pure functions do not change values outside the current scope;
// A negative example
let a = 0
const add = (b) = > a = a + b // Add (1

// Correct example
const add = (a, b) = > a + b
Copy the code
  • Data Immutable: Immutable
// A negative example
const arr = [1.2]
const arrAdd = (value) = > {
  arr.push(value)
  return arr
}

arrAdd(3) / / [1, 2, 3]
arrAdd(3) // [1, 2, 3, 3]

// Positive example
const arr = [1.2]
const arrAdd = (value) = > {
  return arr.concat(value)
}

arrAdd(3) / / [1, 2, 3]
arrAdd(3) / / [1, 2, 3]
Copy the code

In postscript 1, we tidy up whether the array string method has any effect on the original value

  • Function corrification: Converting a function with more than one input parameter to a function with one input parameter;
const add = a= > b => c= > a + b + c
add(1) (2) (3)
Copy the code
  • Partial function: Converts a function with multiple inputs into two parts;
const add = a= > (b, c) => a + b + c
add(1) (2.3)
Copy the code
  • Combinable: Functions can be used in combination
const add = (x) = > x + x
const mult = (x) = > x * x

const addAndMult = (x) = > add(mult(x))
Copy the code

Currie (curry)

Here is an addition function:

var add = (a, b, c) = > a + b + c

add(1.2.3) / / 6
Copy the code

If we have a curry function that wraps the add function and returns a new curryAdd function, we can call it by passing arguments A and b separately.

var curryAdd = curry(add)

// The following output results are the same
curryAdd(1.2.3) / / 6
curryAdd(1.2) (3) / / 6
curryAdd(1) (2) (3) / / 6
curryAdd(1) (2.3) / / 6
Copy the code

Start implementing a Curry function

If the number of parameters passed in does not reach the number of curryAdd, cache the parameters in the closure variable Lists:

function curry(fn, ... args) {
  const length = fn.length
  let lists = args || []

  let listLen
  return function (. _args) {
    lists = [...lists, ..._args]
    listLen = lists.length

    if (listLen < length) {
      const that = lists
      lists = []
      returncurry(fn, ... that) }else if (listLen === length) {
      const that = lists
      lists = []
      return fn.apply(this, that)
    }
  }
}
Copy the code

Code composition (compose)

Now there are toUpperCase, Reverse and head functions, respectively:

var toUpperCase = (str) = > str.toUpperCase()
var reverse = (arr) = > arr.reverse()
var head = (arr) = > arr[0]
Copy the code

We then use them to capitalize the last element of the array as follows:

var reverseHeadUpperCase = (arr) = > toUpperCase(head(reverse(arr)))

reverseHeadUpperCase(['apple'.'banana'.'peach']) // "PEACH"
Copy the code

The reverseHeadUpperCase parameter must be declared manually when constructing the reverseHeadUpperCase function. Similar to the following form:

var reverseHeadUpperCase = compose(toUpperCase, head, reverse)

reverseHeadUpperCase(['apple'.'banana'.'peach']) // "PEACH"
Copy the code

In addition, the compose function is associative and can be used like this:

compose(compose(toUpperCase, head), reverse)
compose(toUpperCase, compose(head, reverse))
Copy the code

Compose (toUpperCase, head, reverse) executes the function from right to left.

In addition, compose and map also have associative laws when used together. The following two methods have equal effects

compose(map(f), map(g))
map(compose(f, g))
Copy the code

Implement the compose function

The code essence is a single line that is used by many open source libraries such as Redux.

var compose = (. args) = > (initValue) => args.reduceRight((a, c) = > c(a), initValue)
Copy the code

Category theory

Category theory is a branch of mathematics. A category can be thought of as a container, where operations on values are now operations on containers. The diagram below:

Learning functional programming is the process of learning functors.

In functional programming, Functor is a container that implements the map function. Functor is regarded as a category below, and the model can be expressed as follows:

class Functor {
  constructor(value) {
    this.value = value
  }

  map(fn) {
    return new Functor(fn(this.value))
  }
}
Copy the code

In functional programming, however, the object-oriented approach of new should be avoided and an of interface, also known as the Conservative functor, exposed instead.

Functor.of = value= > new Functor(value)
Copy the code

Maybe functor

The Maybe functor is used to solve the case where this.value is null:

Maybe.of(null).map(r= > r.toUpperCase()) // null
Maybe.of('m').map(r= > r.toUpperCase())  // Maybe {value: "M"}
Copy the code

The implementation code is as follows:

class Maybe {
  constructor(value) {
    this.value = value
  }

  map(fn) {
    return this.value ? new Maybe(fn(this.value)) : null
  }
}

Maybe.of = value= > new Maybe(value)
Copy the code

Either functor

The Either functor is intended to correspond to if… else… That is, neither left nor right. It can therefore be split into two functors, Left and Right, which can be used as follows:

Left.of(1).map(r= > r + 1)  // Left {value: 1}

Right.of(1).map(r= > r + 1) // Right {value: 2}
Copy the code

Left functor implementation code is as follows:

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

  map(fn) {
    return this
  }
}

Left.of = value= > new Left(value)
Copy the code

The implementation of the Right Functor is as follows:

class Right {
  constructor(value) {
    this.value = value
  }

  map(fn) {
    return new Right(fn(this.value))
  }
}

Right.of = value= > new Right(value)
Copy the code

Either is a filter that calls Either the Left or Right functor. It accepts f, g and a functor Left or Right.

var Either = function(f, g, functor) {
  switch(functor.constructor) {
    case 'Left':
      return f(functor.value)
    case 'Right':
      return g(functor.value)
    default:
      return f(functor.value)
  }
}
Copy the code

Use the demo:

Either((v) = > console.log('left', v), (v) => console.log('def', v), left)   // left 1
Either((v) = > console.log('rigth', v), (v) => console.log('def', v), rigth) // rigth 2
Copy the code

Monad functor

Functors can be nested as follows:

Functor.of(Functor.of(1)) // Functor { value: Functor { value: 1 } }
Copy the code

Monad functors expose the Join and FlatMap interfaces so that callers can flatly nested functors.

class Monad {
  constructor(value) {
    this.value = value
  }

  map(fn) {
    return new Monad(fn(this.value))
  }

  join() {
    return this.value
  }

  flatmap(fn) {
    return this.map(fn).join()
  }
}

Monad.of = value= > new Monad(value)
Copy the code

Usage:

// join
Monad.of(Monad.of(1).join()) // Monad { value: 1 }
Monad.of(Monad.of(1)).join() // Monad { value: 1 }

// flatmap
Monad.of(1).flatmap(r= > r + 1)  / / 2
Copy the code

Monad functors can be used in I/O, which is not pure operation, to turn it into a pure function operation.

Postscript 1: Array string method summary (does it affect the original value)

A method that does not affect the original array

slice
var test = [1.2.3]
var result = test.slice(0.1)

console.log(test)   / / [1, 2, 3]
console.log(result) / / [1]
Copy the code
concat
var test = [1.2.3]
var result = test.concat(4)

console.log(test)   / / [1, 2, 3]
console.log(result) // [1, 2, 3, 4]
Copy the code

A method that affects the original array

Splice (remember this one in particular)
var test = [1.2.3]
var result = test.splice(0.1)

console.log(test)   / / [2, 3]
console.log(result) / / [1]
Copy the code
sort
var arr = [2.1.3.4]
arr.sort((r1, r2) = > (r1 - r2))

console.log(arr) // [1, 2, 3, 4]
Copy the code
reverse
var test = [1.2.3]
var result = test.reverse()

console.log(test)   / / [3, 2, 1)
console.log(result) / / [3, 2, 1)
Copy the code
push/pop/unshift/shift
var test = [1.2.3]
var result = test.push(4)

console.log(test)   // [1, 2, 3, 4]
console.log(result) / / 4
Copy the code

A method that does not affect the original string

substr/substring/slice
// substr
var test = 'abc'
var result = test.substr(0.1)

console.log(test)   // 'abc'
console.log(result) // a

// substring
var test = 'abc'
var result = test.substring(0.1)

console.log(test)   // 'abc'
console.log(result) // a

// slice
var test = 'abc'
var result = test.slice(0.1)

console.log(test)   // 'abc'
console.log(result) // a
Copy the code

reference

  • mostly-adequate-guide
  • Currization of JavaScript topics
  • Functional programming introductory tutorial