简介:欢迎来到函数式程序设计的世界!

在你以往的编码经验中,你很可能习惯使用指令式程序设计(例如 Python 或 Java),即你需要一步步告诉电脑“该如何”执行任务(例如:“开始一个循环、将数值加到变量中、修改该数值”)。

撰写函数式程序则是一种截然不同的思维模式。你不再是给电脑一份会改变内存数值的“待办事项清单”,而是将程序视为一系列数学上的转换(transformations)。这种方式更简洁、更容易测试,且在处理大数据时非常强大!如果一开始觉得有点“反直觉”也不用担心——一旦你掌握了窍门,就会发现它是多么的优雅。

1. 高阶函数 (Higher-Order Functions, HOFs)

函数式程序设计的秘密武器就是高阶函数。通常我们认为函数的输入是数据(如整数或字符串),但在函数式程序设计中,函数是一等公民(first-class objects),这意味着它们可以像其他任何数值一样被处理。

高阶函数是指具备以下至少其中一项特征的函数:

  • 将另一个函数作为参数
  • 函数作为运算结果返回

“工具箱”比喻:
想象一个普通函数是一把铁锤。你用它来处理一根钉子(数据)。
高阶函数就像一只机械手臂。你可以给机械手臂一把铁锤,并指示它去处理房间里的每一根钉子。机械手臂就是那个高阶函数,因为它把“工具”当作输入。

重点总结:高阶函数让我们能够透过将不同的行为“插入”到单一的主要函数中,从而编写出灵活性极高的代码。

2. “三大”高阶函数

AQA 要求你掌握三个专门用于处理列表的高阶函数,分别是 map(映射)、filter(筛选)和 reduce(归纳,有时也称为 fold)。

A. Map (映射)

map 函数会将指定的函数应用于列表中的每一项,并返回一个包含结果的新列表

示例: 如果你拥有一个数字列表 \( [1, 2, 3] \),并对其执行 map “翻倍”函数,你就会得到 \( [2, 4, 6] \)。

记忆技巧:Map 想像成一根魔法棒。你在整排南瓜上挥舞,它们就全都变成了马车。项目的数量不变,但它们的形式改变了。

B. Filter (筛选)

filter 函数会检查列表中的每一项是否符合特定条件(布尔值测试),并返回一个新列表,其中只包含通过测试的项目。

示例: 如果你拥有列表 \( [10, 55, 2, 90] \),并透过 filter 筛选出“大于 50 的数字”,你就会得到 \( [55, 90] \)。

记忆技巧:Filter 想像成夜店门口的保安。他们检查“名单”上的每个人,只放行那些符合“条件”(例如:在嘉宾名单上)的人进场。

C. Reduce (或 Fold,归纳)

reduce 函数会将列表“压缩”成一个单一值。它透过重复将组合函数应用于列表项目来达成此目的。

示例: 如果你拥有列表 \( [1, 2, 3, 4] \),并使用“加法”函数执行 reduce,过程会是:\( 1+2=3 \),接着 \( 3+3=6 \),最后 \( 6+4=10 \)。最终结果为 \( 10 \)。

记忆技巧:Reduce 想像成一个雪球。当它滚过列表时,它会卷入每一项并将其加入自身,直到最后你得到一个巨大的雪球。

快速回顾:
- Map:项目数量相同,数值改变。
- Filter:项目数量减少,数值不变。
- Reduce:最终结果为单一数值。

3. 函数式语言中的列表处理

在函数式程序设计(如 Haskell)中,列表的结构与你可能习惯的数组不同。一个列表基本上由头部 (Head)尾部 (Tail) 组成。

  • Head:列表中的第一个元素。
  • Tail:除头部以外,列表中的其余所有内容
  • 空列表:表示为 []

“火车”比喻:
把列表想像成一列火车。Head火车头Tail 是挂在火车头后面的整列火车厢。如果你把火车头拆下来,剩下的车厢就会变成一列(稍微短一点的)新火车!

常见列表操作:

  1. Return Head:取得第一个项目(例如:\( [5, 8, 2] \) 的 Head 是 \( 5 \))。
  2. Return Tail:取得剩余列表(例如:\( [5, 8, 2] \) 的 Tail 是 \( [8, 2] \))。
  3. Prepend:在前端加入项目(例如:将 \( 10 \) 放到 \( [2, 3] \) 的前端,得到 \( [10, 2, 3] \))。在 Haskell 中通常写作 10:[2, 3]
  4. Append:在末端加入项目。
  5. IsEmpty:检查列表是否为 []。这对于停止递归 (recursion) 至关重要!

你知道吗?函数式程序通常使用递归来代替循环(for/while)。要处理一个列表,函数会处理 Head,然后调用自身来处理 Tail,不断重复直到遇到空列表为止。

4. 重要语言与概念

AQA 提到了一些支持函数式程序设计的语言。你不必精通所有语言,但应该认识它们:

  • 纯函数式:Haskell, Lisp, Scheme, Standard ML。
  • 多重典范(支持函数式特性):Python, C#, Java 8+, VB.NET。

关键点:不可变性 (Immutability)

在函数式程序设计中,数据是不可变的。这意味着一旦列表被建立,你就不能“修改”它。当你使用 map 时,你并没有改变原始列表,而是建立了一个包含转换后数值的全新列表。这能防止“副作用 (side effects)”,让你的代码更具可预测性!

总结:撰写函数式程序涉及使用高阶函数(map, filter, reduce)来处理不可变列表,并透过将列表拆解为头部尾部来进行运算。

5. 应避免的常见错误

1. 混淆 Map 与 Filter:学生常在想要移除项目时使用 map。请记住:Map 无法改变列表的长度;只有 Filter 可以做到。

2. 忘记 Tail 也是一个列表:Head 是单一项目(一个整数、一个字符),但 Tail 永远是一个列表,即使列表内只剩下在一个项目!

3. 用“指令式”思维思考:不要试图在函数式程序中使用计数器(例如 \( i = i + 1 \))。请改用“三大”高阶函数或递归。

如果一开始觉得很棘手,别担心!函数式程序设计就像学骑自行车一样——刚开始摇摇晃晃,直到你的大脑突然找到平衡点。练习在纸上写出 map(double, [1, 2, 3]) 的执行过程,这一切很快就会变得自然。