# 函数式编程

## 函数式编程

### 什么是函数式编程

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"] 不变
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://shenjunhong.gitbook.io/blog/za-wen/han-shu-shi-bian-cheng.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
