🧩 附章 函数式语言是怎样工作的
📄 一句话概括

同一件事两种风格:语句式 6 条语句、4 个中间变量;函数式 1 条语句、0 个中间变量,结果都是 220——函数式不是少写变量,是不写名字。

📝 本节和前面几节的关系

A.2 列了 7 条,这一节开始逐条展开:第 1 条最简单,也最能看出风格差别。

一、实测:两种风格的数量差

语句式 6 条语句:建 total、取范围、筛偶数、求平方、求和、返回。 函数式 1 条语句:一条 return,里面三层函数串起来。 语句式 4 个中间变量(total/nums/evens/squares),函数式 0 个。 两边结果都是 220。

二、不写名字才是关键

起名字的代价不是打字,是要管它:这个名字的值在什么时候是什么? 这就是状态——而状态是 bug 的老家:改了一处、忘了另一处。 函数式把这批名字全砍掉,每一步只是【把上一个结果喂给下一个】。

三、代价是什么

可读性:sum(map(f, filter(g, xs))) 得从里往外读。 调试:出错时栈里全是匿名函数,断点不好下手。 所以不是【函数式更好】,是在【不需要状态】的地方它更省心。

四、动手 Demo:特征1:使用函数编写程序

同一件事写两版:语句式与函数式。 用 ast 数语句条数,并确认结果一致。

def state_way():
    total = 0
    nums = range(1, 11)
    evens = [i for i in nums if i % 2 == 0]
    squares = [i * i for i in evens]
    total = sum(squares)
    return total

def func_way():
    return sum(map(lambda i: i * i,
                   filter(lambda i: i % 2 == 0, range(1, 11))))

print(nstmt(state_way), nstmt(func_way))   # 6 1
print(state_way(), func_way())            # 220 220

实跑输出:

--------------------------------------------------------------------
特征1:使用函数编写程序
--------------------------------------------------------------------

  同一件事(1~10 里偶数平方和)用两种风格写,**数量:**

      语句式:先建中间列表再求和,**每一步都起名字**
      函数式:一条 return,里面 3 层函数串起来,**一个名字都不起**

      语句式语句条数              6 条
      函数式语句条数              1 条
      语句式中间变量              4 个(total/nums/evens/squares)
      函数式中间变量              0 个
      两者结果                    220 与 220

  **函数式的打法不是【少写变量】,是【不写名字】。**
  **因为一旦起了名字,就要管它的值在什么时候是什么**——**这就是状态,也就是 bug 的老家。**
  **代价是可读性**:`sum(map(f, filter(g, xs)))` 得从里往外读。

实测:语句式 6 条语句 4 个中间变量,函数式 1 条语句 0 个,结果都是 220。 函数式的打法不是少写变量,是不写名字——不写名字就不用管它的值。 代价是可读性和可调试性,所以只在不需要状态的地方用。

30 秒实验

  1. 找一个你代码里【建了一堆中间列表】的函数,试着合成一条表达式。
  2. 再判断一下:合成之后别人还能读懂吗?读不懂就说明该拆开。

常见误区

  1. 以为函数式就是写得短。短是结果,不是目的。
  2. 把嵌套三层以上的表达式硬凑出来。那不是函数式,那是炫技。
自测三问(点开对答案) 1. 两版各几条语句? —— 语句式 6 条、函数式 1 条,结果都是 220。 2. 为什么说不写名字是关键? —— 起名字就要管它的值在什么时候是什么,那就是状态,也就是 bug 的老家。 3. 代价是什么? —— 可读性要反向读、调试时栈里全是匿名函数。

相关笔记