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

• 命令式编程

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 = 0const 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}
// 正面示例const arr = [1, 2]const arrAdd = (value) => {  return arr.concat(value)}
arrAdd(3) // [1, 2, 3]arrAdd(3) // [1, 2, 3]

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

### 柯里化(curry)

var add = (a, b, c) => a + b + c
add(1, 2, 3) // 6

var curryAdd = curry(add)
// 以下输出结果都相同curryAdd(1, 2, 3) // 6curryAdd(1, 2)(3) // 6curryAdd(1)(2)(3) // 6curryAdd(1)(2, 3) // 6

#### 动手实现一个 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)))
reverseHeadUpperCase(['apple', 'banana', 'peach']) // PEACH

var reverseHeadUpperCase = compose(toUpperCase, head, reverse)
reverseHeadUpperCase(['apple', 'banana', 'peach']) // PEACH

compose(compose(toUpperCase, head), reverse)compose(toUpperCase, compose(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()) // nullMaybe.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... 的语法, 即非左即右。因此可以将之拆分为 LeftRight 两个函子, 它们的用法如下:

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 1Either((v) => console.log('def', v), (v) => console.log('rigth', v), rigth) // rigth 2

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

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

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)

// joinMonad.of(Monad.of(1).join()) // Monad { value: 1 }Monad.of(Monad.of(1)).join() // Monad { value: 1 }
// flatmapMonad.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
// substrvar test = 'abc'var result = test.substr(0, 1)
console.log(test)   // 'abc'console.log(result) // a
// substringvar test = 'abc'var result = test.substring(0, 1)
console.log(test)   // 'abc'console.log(result) // a
// slicevar 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'