### 命令式编程和声明式编程

• 命令式编程

1.烧开水(为第一人称) 2.拿个茶杯 3.放茶叶 4.冲水

• 声明式编程

1.给我泡杯茶(为第二人称)

``````// 命令式编程
const convert = function(arr) {
const result = []
for (let i = 0; i < arr.length; i++) {
result[i] = arr[i].toLowerCase()
}
return result
}

// 声明式编程
const convert = function(arr) {
return arr.map(r => r.toLowerCase())
}``````

### 什么是函数式编程

• 纯粹性: 纯函数不改变除当前作用域以外的值;
``````// 反面示例
let a = 0
const add = (b) => a = a + b // 两次 add(1) 结果不一致

// 正确示例
const add = (a, b) => a + b``````
• 数据不可变性: Immutable
``````// 反面示例
const arr = [1, 2]
const arrAdd = (value) => {
arr.push(value)
return arr
}

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

// 正面示例
const arr = [1, 2]
const arrAdd = (value) => {
return arr.concat(value)
}

• 函数柯里化: 将多个入参的函数转化为一个入参的函数;
``````const add = a => b => c => a + b + c
• 偏函数: 将多个入参的函数转化成两部分;
``````const add = a => (b, c) => a + b + c
• 可组合: 函数之间能组合使用
``````const add = (x) => x + x
const mult = (x) => x * x

### 柯里化(curry)

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

``````var curryAdd = curry(add)

// 以下输出结果都相同

#### 动手实现一个 curry 函数

``````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 = []
return curry(fn, ...that)
} else if (listLen === length) {
const that = lists
lists = []
return fn.apply(this, that)
}
}
}``````

### 代码组合(compose)

``````var toUpperCase = (str) => str.toUpperCase()
var reverse = (arr) => arr.reverse()
var head = (arr) => arr[0]``````

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

``````var reverseHeadUpperCase = compose(toUpperCase, head, reverse)

``````compose(compose(toUpperCase, head), reverse)

``````compose(map(f), map(g))
map(compose(f, g))``````

#### 动手实现一个 compose 函数

``var compose = (...args) => (initValue) => args.reduceRight((a, c) => c(a), initValue)``

### 范畴论

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

map(fn) {
return new Functor(fn(this.value))
}
}``````

``Functor.of = value => new Functor(value)``

#### Maybe 函子

`Maybe 函子`是为了解决 `this.value` 为 null 的情形, 用法如下:

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

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

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

Maybe.of = value => new Maybe(value)``````

#### Either 函子

`Either 函子` 是为了对应 `if...else...` 的语法, 即`非左即右`。因此可以将之拆分为 `Left``Right` 两个函子, 它们的用法如下:

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

var right = Right.of(1).map(r => r + 1) // Right {value: 2}``````

`Left 函子`实现代码如下:

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

map(fn) {
return this
}
}

Left.of = value => new Left(value)``````

`Right 函子`实现代码如下(其实就是上面的 `Functor`):

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

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

Right.of = value => new Right(value)``````

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

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

``Functor.of(Functor.of(1)) // Functor { value: Functor { value: 1 } }``

`Monad 函子` 对外暴露了 `join``flatmap` 接口, 调用者从而可以扁平化嵌套的函子。

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

map(fn) {
}

join() {
return this.value
}

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

``````// join

// flatmap
Monad.of(1).flatmap(r => r + 1)  // 2``````

### 后记 1: 数组字符串方法小结(是否对原值有影响)

#### 不会对原数组有影响的方法

##### slice
``````var test = [1, 2, 3]
var result = test.slice(0, 1)

console.log(test)   // [1, 2, 3]
console.log(result) // [1]``````
##### concat
``````var test = [1, 2, 3]
var result = test.concat(4)

console.log(test)   // [1, 2, 3]
console.log(result) // [1, 2, 3, 4]``````

#### 对原数组有影响的方法

##### splice(这个需要特别记一下)
``````var test = [1, 2, 3]
var result = test.splice(0, 1)

console.log(test)   // [2, 3]
console.log(result) // [1]``````
##### sort
``````var arr = [2, 1, 3, 4]
arr.sort((r1, r2) => (r1 - r2))

console.log(arr) // [1, 2, 3, 4]``````
##### reverse
``````var test = [1, 2, 3]
var result = test.reverse()

console.log(test)   // [3, 2, 1]
console.log(result) // [3, 2, 1]``````
##### 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``````

#### 不会对原字符串造成影响的方法

##### 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``````
##### replace
``````var test = 'abc'
var result = test.replace('c', 'd')

console.log(test)   // 'abc'
console.log(result) // 'abd'``````