📄 一句话概括
三个课题各一条硬证据:递归 1100 个元素就崩(上限 1000)、朴素 fib(12) 要多算 38 倍、入门要过 6 个概念——都是实打实的门槛,不是偏见。
📝 本节和前面几节的关系
A.12 讲优势,这一节讲代价——两边都看清,才算真认识。
一、实测三个课题
递归有上限:默认 1000 层,超一层直接抛 RecursionError。
纯函数会重复算:算 fib(12) 朴素递归调用 465 次,记忆化只需 12 次,差 38 倍。
概念门槛高:入门要过 6 个(纯函数/高阶/柯里化/模式匹配/类型/单子)。
二、三个课题各自的根
递归上限:来自栈——每层调用占一块栈帧,栈是有限的。 重复计算:来自【不能改东西】——不改就没法缓存中间结果,得自己加记忆化。 概念门槛:来自【必须把副作用隔出去】——单子就是那套隔法。
三、实际怎么绕
递归太深:换循环,或改用库里的迭代实现。
重复计算:加记忆化(functools.lru_cache 一行搞定)。
概念太绕:先只用推导式和纯函数,单子这些等真需要再学。
四、动手 Demo:函数式语言的课题
试探递归上限,量朴素递归与记忆化的调用次数。 两版结果必须一致。
import sys
def rsum(xs):
if not xs:
return 0
return xs[0] + rsum(xs[1:])
try:
rsum(list(range(sys.getrecursionlimit() + 50)))
except RecursionError:
print("超限") # 超限
counter = {"n": 0}
def fib(n):
counter["n"] += 1
return n if n < 2 else fib(n - 1) + fib(n - 2)
fib(12)
print(counter["n"]) # 465
实跑输出:
--------------------------------------------------------------------
函数式语言的课题
--------------------------------------------------------------------
三个课题各给一条硬证据:
① 递归深度:默认上限 1000 层,超一层就抛 RecursionError = True
② 性能:算 fib(12) 朴素版调用了 465 次,记忆化版只需 12 次
③ 学习曲线:入门要过的概念 6 个(纯函数/高阶/柯里化/模式匹配/类型/单子)
两版 fib(12) 结果一致 True
朴素版比记忆化多算 38 倍
**课题一:递归不是免费的**——**深度有上限,超了就崩,这是能不能跑的问题。**
**课题二:纯函数会重复算**——**不改东西就没法缓存,得自己加记忆化。**
**课题三:概念门槛高**——**单子这类东西,是很多人放弃的地方。**
实测:递归超 1000 层直接抛 RecursionError;朴素 fib(12) 调用 465 次,记忆化只需 12 次。
三个课题各有根:栈有限、不能改所以没法缓存、副作用隔离带来概念门槛。
实际都能绕:换循环、加 lru_cache、先不碰单子。
30 秒实验
- 给一段递归函数加
functools.lru_cache,看调用次数降到多少。 - 再把一段深递归改成循环,看深度限制是不是消失了。
常见误区
- 以为递归优雅就可以随便用。深度是硬限制,跟优雅无关。
- 一上手就学单子。先把推导式和纯函数用熟,收益大得多。
自测三问(点开对答案)
1. 递归上限是多少?超了会怎样? —— 默认 1000 层,超了抛 RecursionError。 2. 朴素 fib(12) 调用多少次? —— 465 次;记忆化只需 12 次,差 38 倍。 3. 三个课题怎么绕? —— 深递归换循环、重复算加 lru_cache、概念门槛先只用推导式和纯函数。- ☐ A.14 函数式语言和面向对象的关系
相关笔记
- 函数式语言的优势 — 上一条
- 函数式语言和面向对象的关系 — 两者分工
- 特征6:使用模式匹配和递归来编写循环处理 — 递归那一条的展开
- 🧩 面向对象是怎样工作的 · 索引