欢迎来到函数式列表的世界!

哈啰!今天我们要一起探索列表 (Lists),但这里的列表跟你在 Python 或 Java 中见到的不太一样。在函数式程序设计 (Functional Programming) 中,列表是存储数据最重要的手段。

如果起初觉得这些概念有点“数学化”或陌生,别担心!在指令式程序设计中,我们会告诉计算机“如何”一步步执行任务;而在函数式程序设计中,我们则是描述事物“是什么”。理解列表是掌握这种新思维模式的关键!

1. 什么是函数式列表?

在函数式程序设计中,列表是一组相同数据类型的元素集合。然而,与标准数组不同的是,函数式列表是递归 (recursive) 的。这意味着列表是通过自身来定义的!

一个列表总是以下两者之一:
1. 空列表 (Empty List)(通常记作 \( [ ] \))。
2. 头部 (Head)(第一个元素)连接到一个尾部 (Tail)(尾部本身也是一个列表)。

“火车”的比喻

想象一列玩具火车。
- 头部 (Head) 就是最前端的火车头。
- 尾部 (Tail) 就是后面连接的所有车厢。
- 如果你把车头拆下来,剩下的车厢看起来依然像是一列较短的火车!如果你一直拆下去,最后什么都不会剩下——这就是空列表

重点复习:每个非空列表都有且仅有一个头部和一个尾部(即使尾部只是一个空列表)。

2. 头部与尾部

这是你在 9645 课程中处理任何列表时,最核心的两个“部分”。

头部 (Head)

头部是列表的第一个元素。它是一个单一的项目(例如整数或字符)。
示例:在列表 \( [5, 8, 2, 1] \) 中,头部是 \( 5 \)。

尾部 (Tail)

尾部是头部之后的所有其他内容。关键在于,尾部始终本身也是一个列表
示例:在列表 \( [5, 8, 2, 1] \) 中,尾部是 \( [8, 2, 1] \)。

记忆小撇步:头与尾的技巧

- Head(头部)就像 Hat(帽子,戴在最上面/最前面)。
- Tail(尾部)就像 Train(火车,跟在后面的整串车厢)。

3. 列表的标记法与构造

在考试中,你可能会看到几种列表的写法。最常见的是使用 Cons 运算符(这是“construct”,即构造的缩写)。

Cons 运算符 \( (:) \) 或 \( (|) \)

我们用它将头部接上尾部来构成一个新列表。
语法通常看起来像这样:\( Head : Tail \)。

示例解析:
我们如何建立列表 \( [10, 20, 30] \)?
1. 从空列表开始:\( [ ] \)
2. 加入 30:\( 30 : [ ] \) 变成 \( [30] \)
3. 再加入 20:\( 20 : [30] \) 变成 \( [20, 30] \)
4. 再加入 10:\( 10 : [20, 30] \) 变成 \( [10, 20, 30] \)

常见错误:学生常以为 \( [5] \) 的尾部什么都没有。实际上,\( [5] \) 的尾部是空列表 \( [ ] \)。切记:尾部必须永远是一个列表!

4. 列表的运算

由于函数式程序设计不使用“循环”(如 forwhile),我们改用列表的特性来处理数据。

列表是否为空?

函数式语言有内建方法来检查列表是否为 \( [ ] \)。这非常重要,因为它告诉我们的函数何时该停止(这是基本案例/基底情况,Base Case)。

提取数值

我们使用模式匹配 (pattern matching) 来拆解列表。
如果我们有一个列表 \( L = [x : xs] \):
- \( x \) 代表头部
- \( xs \) 代表尾部(字母 's' 代表复数,意指剩余的更多个 x)。

重点总结:
\( Head([1, 2, 3]) = 1 \)
\( Tail([1, 2, 3]) = [2, 3] \)

5. 为什么要在此函数式程序设计中使用列表?

你知道吗? 在函数式程序设计中,列表是不可变的 (immutable)。这意味着一旦列表被建立,就无法修改。如果你想“新增”一个项目,你实际上是通过将新的头部“Cons”到旧列表上,来建立一个全新的列表

这使得程序更加可预测,并有助于防止数据在意外中被更动而产生的臭虫 (bugs)!

总结检查表

在继续学习之前,请确保你能:
- 识别任何列表的头部(它是一个元素)。
- 识别任何列表的尾部(它是一个列表)。
- 解释空列表表示为 \( [ ] \)。
- 理解 \( 1 : [2, 3] \) 与 \( [1, 2, 3] \) 是相同的。
- 认知到单一项目列表的尾部就是空列表。

如果觉得这些概念有点抽象,别担心!只要多练习“拆解”和“组合”这些列表,一切都会变得自然顺手。你做得很好!