> For the complete documentation index, see [llms.txt](https://shenjunhong.gitbook.io/blog/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://shenjunhong.gitbook.io/blog/za-wen/han-shu-shi-bian-cheng.md).

# 函数式编程

## 函数式编程

### 什么是函数式编程

SQL 语句就是声明式的，你无需关心 Select 语句是如何实现的，不同的数据库会去实现它自己的方法并且优化。

所谓惰性执行指的是函数只在需要的时候执行，即不产生无意义的中间变量 函数式编程跟命令式编程最大的区别就在于几乎没有中间变量，它从头到尾都在写函数，只有在最后的时候才通过调用 convertName 产生实际的结果。

这是函数式编程的核心概念：

* 数据不可变： 它要求你所有的数据都是不可变的，这意味着如果你想修改一个对象，那你应该创建一个新的对象用来修改，而不是修改已有的对象。
* 无状态： 主要是强调对于一个函数，不管你何时运行，它都应该像第一次运行一样，给定相同的输入，给出相同的输出，完全不依赖外部状态的变化。

#### 对比这两段代码

```js
const curUser = {
  name: "Peter",
};

const saySth = (user, str) => user.name + ": " + str; // 不依赖外部变量
const changeName = (user, name) => ({ ...user, name }); // 未修改外部变量

const newUser = changeName(curUser, "Jay"); // { name: 'Jay' }
saySth(curUser, "hello!"); // Peter: hello!
```

```js
const curUser = {
  name: "Peter",
};

const saySth = (str) => curUser.name + ": " + str; // 引用了全局变量
const changeName = (obj, name) => (obj.name = name); // 修改了输入参数
changeName(curUser, "Jay"); // { name: 'Jay' }
saySth("hello!"); // Jay: hello!
```

## 柯里化的意思是将一个多元函数，转换成一个依次调用的单元函数。

```
f(a,b,c) → f(a)(b)(c)
```

部分函数应用强调的是固定一定的参数，返回一个更小元的函数。

```js
// 柯里化
f(a,b,c) → f(a)(b)(c)
// 部分函数调用
f(a,b,c) → f(a)(b,c) / f(a,b)(c)
```

柯里化强调的是生成单元函数，部分函数应用的强调的固定任意元参数，而我们平时生活中常用的其实是部分函数应用,这样的好处是可以固定参数，降低函数通用性，提高函数的适合用性.

```js
// 假设一个通用的请求 API
const request = (type, url, options) => ...
// GET 请求
request('GET', 'http://....')
// POST 请求
request('POST', 'http://....')

// 但是通过部分调用后，我们可以抽出特定 type 的 request
const get = request('GET');
get('http://', {..})
```

把要操作的数据放最后面

```js
const split = curry((x, str) => str.split(x));
const join = curry((x, arr) => arr.join(x));
const replaceSpaceWithComma = compose(join(","), split(" "));
const replaceCommaWithDash = compose(join("-"), split(","));
```

函数组合的 debug 可以用 trace

```js
const trace = curry((tip, x) => {
  console.log(tip, x);
  return x;
});
const lastUppder = compose(toUpperCase, head, trace("after reverse"), reverse);
```

```js
// PointFree  没有出现需要操作的参数
const upperLastItem = compose(toUpperCase, head, reverse);

// 非 PointFree 出现了需要操作的参数
const upperLastItem = (arr) => {
  const reverseArr = arr.reverse();
  const head = reverseArr[0];
  return head.toUpperCase();
};
```

```js
const Box = (x) => ({
  map: (f) => Box(f(x)),
  inspect: () => `Box(${x})`,
});

const finalPrice = (str) =>
  Box(str)
    .map((x) => x * 2)
    .map((x) => x * 0.8)
    .map((x) => x - 50);

const result = finalPrice(100);

console.log(result); // => Box(110)
```

new 让人误以为是创建了 Class 的实例，但其实根本不存在所谓的实例化，只是简单的属性委托机制(对象组合的一种)，而 this 则引入了执行上下文和词法作用域的问题，而我只是想创建一个简单的对象而已！

map 知道如何在上下文中映射函数值。它首先会打开该容器，然后把值通过函数映射为另外一个值，最后把结果值再次包裹到一个新的同类型的容器中。而这种变换容器内的值的方法(map)称为 Functor(函子)。

map 是把函数执行的结果重新包装到 Box 中后然返回一个新的 Box 类型，而 forEach 则是直接把函数执行的结果 return 出来，就结束了！

```js
```

什么是 functor

上面我们定义了一个简单的 Box，其实也就是拥有 map 和 fold 方法的类型。让我们把脚步放慢一点，再仔细观察和思考一下我们的 map：Box(a) -> Box(b) ，本质上就是通过一个函数 a -> b 把一个 Box(a) 映射为 Box(b)。这和中学代数中的函数知识何其类似，不妨再回顾一下代数课本中函数的定义

假设 A 和 B 是两个集合，若按照某种对应法则，使得 A 的任一元素在 B 中都有唯一的元素和它对应，则称这种对应为从集合 A 到集合 B 的函数。

常见的 functor

* Array 的 map 和 filter。
* jQuery 的 css 和 style。
* Promise 的 then 和 catch 方法(Promise 也是一种 Functor? Yes！)。
* Rxjs Observable 的 map 和 filter (异步函数的组合？Relax！)。

```js
[1, 2, 3].map((x) => x + 1).filter((x) => x > 2);

$("#mybtn")
  .css("width", "100px")
  .css("height", "100px")
  .css("background", "red");

Promise.resolve(1)
  .then((x) => x + 1)
  .then((x) => x.toString());

Rx.Observable.fromEvent($input, "keyup")
  .map((e) => e.target.value)
  .filter((text) => text.length > 0)
  .debounceTime(100);
```

```js
const partial =
  (fn, ...presetArgs) =>
  (...laterArgs) =>
    fn(...presetArgs, ...laterArgs);

const double = (n) => n * 2;
const map = (fn, F) => F.map(fn);
const mapDouble = partial(map, double);

const res = mapDouble(Box(1)).fold((x) => x);
console.log(res); // => 2
```

const componse = (...fn) => x => (fn.reduceRight(x,v) => x + v)

先对比下功能强大的 box 理念, 也就是最基本的 functor,

### 函数式编程实践

#### 数组的增删改查

1. 增加

```js
const arr = [1, 2, 3, 4, 5];
const newArr = [...arr, 6];
```

2. 删除

```js
const arr = [1, 2, 3, 4, 5];
const newArr = arr.filter((item) => item !== 3);
```

3. 修改

```js
const arr = [1, 2, 3, 4, 5];
const newArr = arr.map((item) => (item === 3 ? 6 : item));
```

4. 查找

```js
const arr = [1, 2, 3, 4, 5];
const newArr = arr.find((item) => item === 3);
```

#### 对象的增删改查

1. 增加

```js
const obj = { a: 1, b: 2 };
const newObj = { ...obj, c: 3 };
```

2. 删除

```js
const obj = { a: 1, b: 2 };
const { a, ...newObj } = obj;
```

3. 修改

```js
const obj = { a: 1, b: 2 };
const newObj = { ...obj, a: 3 };
```

4. 查找

```js
const obj = { a: 1, b: 2 };
const a = obj.a;
```

#### 数组和对象的组合

1. 增加

```js
const arr = [
  { id: 1, name: "a" },
  { id: 2, name: "b" },
  { id: 3, name: "c" },
];
const newArr = [...arr, { id: 4, name: "d" }];
```

2. 删除

```js
const arr = [
  { id: 1, name: "a" },
  { id: 2, name: "b" },
  { id: 3, name: "c" },
];
const newArr = arr.filter((item) => item.id !== 2);
```

3. 修改

```js
const arr = [
  { id: 1, name: "a" },
  { id: 2, name: "b" },
  { id: 3, name: "c" },
];
const newArr = arr.map((item) =>
  item.id === 2 ? { ...item, name: "d" } : item
);
```

### js 改变原数组和不改变原数组的方法

#### 改变原数组的方法

1. push

```js
let a = arr.push("f");
console.log(a); // 5 返回数组长度
console.log(arr); // ['a', 'b', 'c', 'd', 'f']
```

2. pop 删除最后一个元素，并返回删除的元素

```js
let a = arr.pop();
console.log(a); // d
console.log(arr); // ['a', 'b', 'c']
```

3. shift 删除第一个元素，并返回删除的元素

```js
let a = arr.shift();
console.log(a); // a
console.log(arr); // ['b', 'c', 'd']
```

4. unshift 在数组的开头添加一个或更多元素，并返回新的长度

```js
let a = arr.unshift(0);
console.log(a); // 5 返回数组长度
console.log(arr); // [0, 'a', 'b', 'c', 'd']
```

5. splice 从数组中添加或删除元素

```js
let a = arr.splice(1, 2, "f");
console.log(a); // 返回被删除的元素数组['b', 'c']
console.log(arr); // 在添加的地方添加元素后的数组["a", "f", "d"]
```

6. sort 对数组进行排序

```js
let arr = ["c", "a", "d", "b"];
let a = arr.sort();
console.log(a); // ['a', 'b', 'c', 'd']
console.log(arr); // ['a', 'b', 'c', 'd']
```

7. reverse 反转数组

```js
let a = arr.reverse();
console.log(a); // ["d", "c", "b", "a"]
console.log(arr); // ["d", "c", "b", "a"]
```

8. fill 用一个固定值填充数组

```js
let a = arr.fill("e", 2, 4);
console.log(a); // 返回它会改变调用它的 `this` 对象本身, 然后返回它['a', 'b', 'e', 'e']
console.log(arr); // ['a', 'b', 'e', 'e']
```

9. copyWithin 从数组的指定位置拷贝元素到数组的另一个指定位置中

```js
let a = arr.copyWithin(1, 2, 3);
console.log(a); //返回被复制的元素数组 ['a', 'c', 'c', 'd']
console.log(arr); //原元素数组已经改变 ['a', 'c', 'c', 'd']
```

10. map： 只有当 arr 为基本数据类型时，map 方法才不会改变原始数组，arr 为引用类型时，还是会改变原数组

```js
let arr = [{ a: 1 }, { b: 2 }];
let a = arr.map((item) => (item.a = 2));
console.log(a); // [2, undefined]
console.log(arr); // [{a: 2}, {b: 2}]
```

#### 不改变原数组的方法

1. concat 连接两个或多个数组

```js
let a = arr.concat(["e", "f"]);
console.log(a); // 返回一个新数组 ['a', 'b', 'c', 'd', 'e', 'f']
console.log(arr); // 原数组不变 ['a', 'b', 'c', 'd']
```

2. slice 从已有的数组中返回选定的元素

```js
let a = arr.slice(1);
console.log(a); // ["b", "c", "d"]
console.log(arr); // ["a", "b", "c", "d"] 不变

slice(-1); // ["d"]
```

3. join 将数组（或一个类数组对象）的所有元素连接成一个字符串并返回这个字符串

```js
let a = arr.join("-");
console.log(a); // "a-b-c-d"
console.log(arr); // ["a", "b", "c", "d"] 不变
```

4. filter 过滤数组

```js
let a = arr.filter((item) => item !== "b");
console.log(a); // ["a", "c", "d"]
console.log(arr); // ["a", "b", "c", "d"] 不变
```

5. reduce 对数组中的每个元素执行一个由您提供的“reducer”函数（升序执行），将其结果汇总为单个返回值

```js
let a = arr.reduce((pre, cur) => pre + cur);
console.log(a); // "abcd"
console.log(arr); // ["a", "b", "c", "d"] 不变
```

6. every 对数组中的每一项运行给定函数，如果该函数对每一项都返回 true，则返回 true

```js
let a = arr.every((item) => item === "a");
console.log(a); // false
console.log(arr); // ["a", "b", "c", "d"] 不变
```

7. some 对数组中的每一项运行给定函数，如果该函数对任一项返回 true，则返回 true

```js
let a = arr.some((item) => item === "a");
console.log(a); // true
console.log(arr); // ["a", "b", "c", "d"] 不变
```

8. find 返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined

```js
let a = arr.find((item) => item === "a");
console.log(a); // "a"
console.log(arr); // ["a", "b", "c", "d"] 不变
```

9. findIndex 返回数组中满足提供的测试函数的第一个元素的索引。否则返回 -1

```js
let a = arr.findIndex((item) => item === "a");
console.log(a); // 0
console.log(arr); // ["a", "b", "c", "d"] 不变
```
