函数式编程

函数式编程

入职了一家新公司,原本以为技术会很落后的,以为进去后也不过是画画页面 ,调用接口。结果进去第一天就发现技术都是最新的 ,业务需求都很简单,但是接手的代码都好新,看代码风格也都是满满的react风格。 vue3+tsx +函数式编程,每一项技术都需要重新学,头疼。

趁周末偷偷充下电,做做笔记,毕竟因为技术太菜被嫌弃就丢人丢大发了! 也好,学习新的东西对自己的职业生涯也是大有帮助!

JavaScript 函数式编程是指使用函数来进行编程的一种范式,在函数式编程中,函数被视为一等公民,可以作为变量、参数和返回值来使用

  • 纯函数 相同的输入一定会产生相同的输出 常见的就是修改外部某个变量,影响了其他函数的执行
  • 高阶函数 接受一个或多个函数为参数或者返回一个函数作为结果的函数
  • 函数组合: 将多个函数按照一定的顺序和方式组合起
  • 惰性计算: 只有在必要的时候才计算结果
  • 不可变数据: 数据结构在被创建之后不能被修改,只能通过复制来创建新的数据结构

高阶函数:

1
2
3
4
5
const add = (a, b) => a + b;
const subtract = (a, b) => a - b;
const calculate = (fn, a, b) => fn(a, b);
calculate(add, 1, 2);
calculate(subtract, 4, 1);

偏函数

偏函数是一种将多个参数的函数转换为接受部分参数的新函数的技术。它基于现有函数创建一个新函数,该新函数在调用时只需要提供其定义中的一些参数,而不是所有必需的参数。一般通过bind方式处理

1
2
3
4
5
6
7
function multiply(a, b) {
return a * b;
}

const double = multiply.bind(null, 2);

console.log(double(5)); // 输出 10

柯里化 Currying

柯里化是一种将接受多个参数的函数转换为一系列只接受单个参数的函数的技术。这使我们更容易创建可组合、通用和模块化的函数。

1
2
3
4
5
6
7
8
9
function add(a) {
return function (b) {
return a + b;
}
}

const addOne = add(1);

console.log(addOne(5));

将一个包含多个参数的函数转换成另一个函数,这个函数如果被给到的参数少于正确的数量,就会返回一个接受剩余参数的函数。

1
2
3
const add = (x, y) => x + y

const curriedAdd = _.curry(add)

函数组合

函数组合是指将两个或多个函数结合在一起以产生一个新函数的技术,管道运算符(Pipeline Operator)和compose函数。

pip 运算符 ES2021

1
const result = 3 |> add(2) |> square;

compose函数,compose函数是一个接受两个或多个函数作为参数并返回一个新函数的高阶函数, 该新函数将把传入的值作为最右边的函数的输入,并将结果带回到最左边的函数

1
2
3
4
5
6
const add => (a,b)=> a+b
const sqr=>a=>a*a
const compose=(...fn)=>(arg)=>fn.reduce((acc, cur)=>cur(acc),arg);
const result = compose(sqr,add.bind(null,2))(3)
console.log(result) //输出11 3*3+2

纯函数 Purity

  • 可缓存性:由于纯函数的输出只取决于其输入,所以我们可以将它们的执行结果缓存起来
  • 可测试性:因为纯函数不依赖于外部状态,所以它们更容易被测试和调试,也更容易推理。
  • 并行代码:由于纯函数没有任何共享状态,所以它们更容易在并行环境中工作。

副作用 (Side Effects)

目标是尽可能地使用纯函数,但是在某些情况下,副作用是无法避免的

  • I/O 操作:读写文件、网络请求
  • 状态管理:有时候我们需要在函数执行过程中记录一些状态信息
  • DOM 操作:JavaScript通常用于HTML文档的交互

幂等(Idempotent)

幂等(Idempotent)是指对于同一输入值,函数的输出结果总是相同的.无论调用函数多少次,返回的结果都是一样的

  • 操作数据库,执行多次也只是插入一次
  • 缓存数据
  • 网络重新试,调用多次,仅仅只执行一次

Point-Free

它的核心思想是将函数组合起来,并通过函数组合实现业务逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 命令式编程(Imperative programming)
function add(a, b) {
return a + b;
}

function square(n) {
return n * n;
}

const result = square(add(1, 2));

console.log(result); // 9

// Point-Free 风格(Point-Free style)
const add = (a, b) => a + b;
const square = n => n * n;

const result = [1, 2].reduce(add, 0);
const squareResult = square(result);

console.log(squareResult); // 9

契约 (Contracts)

在函数式编程中,契约(Contracts)是指一种基于先决条件和后置条件的编程范式,它描述了一个函数或模块的行为和输入输出之间的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const sendMessage = contract((message, callback) => {
contract.assert(
contract.typeOf("string", message),
'sendMessage(message, callback): message should be a string'
);
contract.assert(
contract.typeOf("function", callback),
'sendMessage(message, callback): callback should be a function'
);

// 实际发送逻辑
callback(null, "Message sent successfully!");
});

sendMessage("Hello World", (err, result) => {
if (err) {
console.error(err);
} else {
console.log(result);
}
});

范畴 (Category)

在 JavaScript 函数式编程中,我们可以将范畴看作是由一些函数和数据类型组成的集合,并通过这些函数和数据类型之间的关系来描述和处理问题。

  • 函数构成范畴

  • Functor 范畴 函子

  • Monad 范畴 PromiseIO 都是 Monad 的例子。Monad 主要用于异步操作、异常处理等场景

值类型

在 JavaScript 函数式编程中,值(Value)是指一种数据结构,它主要用于表示状态不可变的数据。值类型包括基本类型(如 number、string、boolean)以及复合类型(如数组、对象等),值类型的特点是不可变性,即在创建之后它们的值不能被修改。有利于编写纯函数

immutable.js 库就提供了一系列的数据类型(如 List、Map、Set 等),这些数据类型都是不可变的,只能通过创建新的对象来修改它们的状态。

常量

在 JavaScript 中,我们通常使用 Object.freeze() 方法将一个对象转换为常量对象,使得该对象的值不能被修改。定义常量对象的示例如下:

1
2
3
4
5
const COLORS = Object.freeze({
RED: '#ff0000',
GREEN: '#00ff00',
BLUE: '#0000ff'
});

函子 (Functor)

函子(Functor)是一种特殊的对象类型,它可以看作是一个容器,用于封装一些值,并提供一些方法来操作这些值。函子实际上是一种抽象的概念,它并不限定具体的实现方式,在实际应用中可以使用数组、对象等数据结构来实现。处理不纯的函数副作用

抬升 (Lift)

JavaScript 函数式编程中,抬升(Lift)是一种将普通函数转换为可用于处理函子对象的高阶函数的技术。通过使用抬升,我们可以将多个函数组合起来,形成一个新的函数,用于操作函子对象。

具体实现方式是,我们首先定义一个抬升函数 lift(),该函数接受一个或多个普通函数作为参数,并返回一个新的函数,该新函数可以接受一个或多个函子对象,并将这些函子对象传递给原有函数进行处理,最终返回包含处理结果的新的函子对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 定义函子对象 Maybe
class Maybe {
constructor(value) {
this.value = value;
}

// 如果有值就执行传入的处理函数
map(fn) {
return this.value ? new Maybe(fn(this.value)) : new Maybe(null);
}
}

// 抬升函数 lift()
function lift(fn) {
return function (...args) {
const first = args[0];
if (first instanceof Maybe) {
// 如果是 Maybe 对象,则调用 map() 方法处理
return first.map(function (value) {
// 调用原始函数处理
return fn(...[value, ...args.slice(1)]);
});
} else {
// 否则直接调用函数
return fn(...args);
}
};
}

// 定义两个普通函数
const add = (a, b) => a + b;
const multiply = (a, b) => a * b;

// 使用抬升处理两个函子对象
const liftedAdd = lift(add);
const liftedMultiply = lift(multiply);
const result = liftedAdd(new Maybe(5), new Maybe(4)).map((res1) =>
liftedMultiply(res1, new Maybe(10))
);

console.log(result.value); // 输出: "90"

引用透明性 (Referential Transparency)

在 JavaScript 函数式编程中,引用透明性(Referential Transparency)是指一个函数的输出只与它的输入有关,而不依赖于其他外部状态或变量。。

不依赖外部环糊

等式推理 (Equational Reasoning)

在 JavaScript 函数式编程中,等式推理(Equational Reasoning)是指使用等式来推导代码的正确性。也就是说,我们可以通过等式关系来证明某个函数的正确性,而不需要运行它。这个过程主要依赖于引用透明性,即一个函数的输出只由输入决定,不受外部状态影响。

等式推理可以帮助我们更好地理解一段代码的含义和作用,并且能够快速地发现可能存在的错误。在函数式编程中,我们把每个函数看做是数学上的一个函数,也就是说,它们只接受输入参数并返回输出结果,不存在副作用,因此可以进行等式推导。

实际就是方便测试 也没啥意义

典型应用

阅读一些典型的代码块

  1. export const tap = (fn) => async (data) => {

    await fn(data);

    return data;

};

只是异步做一些事情,不影响主流程,输入是多少 输出是多少。 通常用于更新视图,操作dom本身也是释放副作用

  1. export const namespace = (name:any) => (payload:any) => async ( stream ) => ({ …stream, [name]: typeof payload === “function” ? await payload(stream[name], stream) : payload });

不清楚

namespace(‘prop’) (payload) stream ==》 {

…stream ,

prop: payload(stream [prop], payload ) | payload

}

3.

  1. export const spaceslide = (from, to) => (fn) => namespace(to)(async (_, stream) => await fn(stream[from]));


函数式编程
https://godbuttton.github.io/2023/08/12/函数式编程/
作者
godbutton
发布于
2023年8月12日
许可协议