函数式编程简介

2025-06-05

函数式编程简介

这篇文章源自我在2019 年博伊西代码训练营上展示的一个例子,比较了命令式和函数式两种解决问题的方法。我的目的并非教授函数式编程的全部内容,而是介绍一种超越传统方法(循环、变异等)的全新思维方式。拥有不同的参考框架,在面对问题时就能获得更多工具。

函数式编程的基础可以用三个主要思想来表达:

  • 不可变数据结构
  • 纯函数
  • 一等函数

让我们快速了解一下这些项目符号的含义。

不可变数据结构

在使用 JavaScript 等编程语言时,我们可以将数据赋值给变量let myVariable = 5;。但是,我们仍然可以稍后将变量重新赋值给其他变量myVariable = "Now I'm a string."。这可能很危险——也许另一个函数依赖的myVariable是一个数字,或者如果一些异步函数同时在执行,该怎么办myVariable?我们可能会遇到合并冲突。

例子
const obj = {
  a: 1,
  b: 2
};

function addOne(input) {
  return {
    a: input.a + 1,
    b: input.b + 1
  };
}

const newObj = addOne(obj);

newObj === obj; // false
Enter fullscreen mode Exit fullscreen mode

纯函数

纯函数没有副作用。这是什么意思呢?好吧,一个仅根据输入计算输出的函数可以被认为是纯函数。如果我们的函数接受一个输入,执行数据库更新,然后返回一个值,那么我们的代码就包含了一个副作用——更新数据库。多次调用该函数可能不会总是返回相同的结果(例如内存不足、数据库被锁定等等)。拥有纯函数对于我们编写无错误、易于测试的代码至关重要。

例子
function notPureAdd(a, b) {
  return a + new Date().getMilliseconds();
}

function pureAdd(a, b) {
  return a + b;
}
Enter fullscreen mode Exit fullscreen mode

一等函数

“一等公民”这个术语可能看起来很奇怪,但它的意思是函数可以像其他数据类型一样被传递和使用。例如,字符串、整数、浮点数等等。支持一等公民函数的编程语言允许我们将函数传递给其他函数。可以将其想象成依赖注入。如果你使用过 JavaScript,就会知道一等公民函数无处不在,我们将在接下来的示例中更详细地介绍它们。

例子
// robot expects a function to be passed in
function robot(voiceBox) {
  return voiceBox("bzzzz");
}

// console.log is a function that logs to the console
robot(console.log);
// alert is a function that shows a dialog box
robot(alert);
Enter fullscreen mode Exit fullscreen mode

命令式编程和函数式编程的比较

为了展示命令式和函数式编程之间的基本比较,让我们将数组中的数字相加[1, 2, 3, 4]并得到其总和。

我们可能会这样写:

const list = [1, 2, 3, 4];

let sum = 0;

for (let i = 0; i < list.length; i++) {
  sum += list[i];
}

console.log(sum); // 10
Enter fullscreen mode Exit fullscreen mode

将其转换为函数式风格后,我们遇到了一个大问题。sum列表的每次迭代都会产生不同的值。记住……不可变的数据结构。

为了使此代码能够运行,让我们分解一下如何计算总和。

首先,我们从某个值开始,在我们的例子中是0(参见行let sum = 0;)!接下来,我们取出数组中的第一个元素1并将其添加到我们的和中。现在我们得到了0 + 1 = 1。然后我们重复此步骤,取出2并添加到和中1 + 2 = 3。如此反复,直到遍历完数组的长度。

换个角度来理解一下:

0 + [1,2,3,4]
0 + 1 + [2,3,4]
1 + 2 + [3,4]
3 + 3 + [4]
6 + 4
10
Enter fullscreen mode Exit fullscreen mode

我们可以将这个算法视为两个独立的函数,首先我们需要某种方法将数字相加。

function add(a, b) {
  return a + b;
}
Enter fullscreen mode Exit fullscreen mode

简单的!

接下来,我们需要找到一种循环遍历给定数组的方法。由于大多数函数式编程通常依赖于递归而不是循环,因此我们将创建一个递归函数来循环遍历数组。我们来看看它是什么样子的。

function loop(list, index = 0) {
  if (!list || index > list.length - 1) {
    // We're at the end of the list
    return;
  }

  return loop(list, index + 1);
}
Enter fullscreen mode Exit fullscreen mode

在这个函数中,我们传入要循环的列表和一个索引,用于确定当前在列表中的位置。如果到达列表末尾,或者传入了一个无效的列表,则循环结束。如果没有到达,则loop再次调用,并增加索引。尝试在console.log(list[index])循环函数内部,在 之前添加一个return loop(list, index + 1);!我们应该会1 2 3 4在控制台上看到打印的内容!

为了最终对数组求和,我们需要将loopadd函数结合起来。在阅读此示例时,请记住上面的算法:

function loop(list, accu = 0, index = 0) {
  if (!list || index > list.length - 1) {
    return accu;
  }

  const result = add(accu, list[index]);

  return loop(list, result, index + 1);
}
Enter fullscreen mode Exit fullscreen mode

我们重新安排了函数中的一些参数loop。现在我们有一个accu参数(accumulation),它将跟踪列表中给定位置的总和。我们还直接使用函数获取与列表中当前项相加add的结果。如果这样,我们应该将结果打印到控制台上!accuconsole.log(loop(list));10

我们再进一步怎么样?如果我们不想对数字列表求和,而是将它们相乘呢?现在,我们必须复制loop函数,粘贴它,然后改成add其他形式(multiply也许?)。真是麻烦!还记得一等函数吗?我们可以在这里运用这个想法,使我们的代码更加通用。

function loop(func, list, accu = 0, index = 0) {
  if (!list || index > list.length - 1) {
    return accu;
  }

  const result = func(accu, list[index]);

  return loop(func, list, result, index + 1);
}
Enter fullscreen mode Exit fullscreen mode

在上面的例子中,唯一的变化是我们现在添加了一个loop接受函数的新参数。add我们将调用传入的函数来获取结果,而不是像上面那样。现在我们可以非常轻松地对列表进行addmultiply、等操作了。subtract

  • loop(add, list);
  • loop(function(a, b) { return a * b; }, list);

我们不再只是循环遍历数组,而是像折叠纸张一样折叠数组,直到得到一个结果。在函数式编程中,这个函数可能被称为fold,而在 JavaScript 中,我们将其理解为reduce

function reduce(func, list, accu = 0, index = 0) {
  if (!list || index > list.length - 1) {
    return accu;
  }

  const result = func(accu, list[index]);

  return reduce(func, list, result, index + 1);
}
Enter fullscreen mode Exit fullscreen mode

结尾

我们学习了函数式编程的基础知识,以及如何通过分解问题来为同一问题提供不同的解决方案。被视为其他操作(例如或 )reduce的基础。这是我的测试,我们如何仅使用刚刚创建的来实现这两个函数?map()filter()reduce()

暗示

还记得 reduce 算法吗?

0 + [1,2,3,4]
0 + 1 + [2,3,4]
1 + 2 + [3,4]
3 + 3 + [4]
6 + 4
10
Enter fullscreen mode Exit fullscreen mode

如果我们不从...开始,而是0从数组开始,会怎么样[]

回答

文章来源:https://dev.to/ganderzz/introduction-to-function-programming-598e
PREV
第一部分:将 Typescript 与 React 结合使用 第一部分:将 Typescript 与 React 结合使用
NEXT
造成数千美元损失的错误(Kubernetes、GKE)重大危险信号🚩