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