分类: 编程 标签: accumulator lambda closure

本篇是关于《黑客与画家》中 Paul 用累加器作为例子营销 Lisp,第一次读他的书时,还看不太懂,多年后我大约能理解一点了。我想就此问题本身和此问题的外延讨论一番,当然,你也看到本文的成熟度为 3 / 5,说明随着我对技术的理解,后续会持续修订。

了解本篇探讨的内容时,需先了解一点背景。Paul Graham 因创办 Y Combinator (创业公司孵化器)而成为美国“创业教父”。在他的随笔集《黑客与画家》中举了一个例子,用 lisp 编写累加器来说明 lisp 语言能力。怎么理解这个累加器,其实每个人都见过,基本上可等于计算器上的那个 M+ 键在计算机软件中的实现。其次,Lisp 是一门如此久远而特别的编程语言,用 Paul Graham 《On Lisp》一书中的话来说,就是 lisp 是一种可编程的编程语言。

这里的可编程,也类似编程语言中的 Lambda(常以希腊字母 λ 表示)以及宏代表的元编程能力,Ruby 语言的作者特别描述了这块,Y Combinator Y 也许也有此意义。

累加器问题本身

Paul 提出一个类似编程问题:写一个函数,它能够生成累加器,即这个函数接受一个参数 n,然后返回另一个函数,后者接受参数 i,然后返回 n 增加了 i 后的值。就是说,写一个能生成函数的函数。

Common Lisp 的实现为:

(defun foo(n)
  (lambda (i) (incf n i)))

调用方式和结果可以是:

(setq acc (foo 2))
(print (funcall acc 6)) ; 8
(print (funcall acc 7)) ; 15

并且举出了 Ruby、Perl、Smarttalk、JavaScript 的实现,并列举了 Fortran、C、C++、Java、Visual Basic 似乎无法实现或优雅的实现。

特别的,Paul 强调了 Python 貌似比 Perl 优雅,但实现起来由于 lambda 支持的局限,也未能有类似实现。

从逻辑上,似乎是仅针对 Python 语言设计的一个偏好来对比它,未免显得不公。Python 的实现更函数式一点,也是表达了 Guido 对 Python 语言设计的取舍,弱化的 lambda。Lisp 其实算是函数式的鼻祖,但上面的累加函数反而不够满足,它对相同的输入,返回不同的值,因为它不够内聚,对外依赖初始值这个外部状态,不利于没有上下文时去理解程序,且 lambda 内部引用了 n 还修改了 n 的值,就好比你有一个钱包,当你第一次放了 10 块钱进去后,你发现它同时拿走了你上衣口袋的 8 块钱,第二次放了 10 块钱进去时,它又拿走了 7 块。(这个函数的实现上和学院教授的软件质量是对立的,这样的巧妙实现也许在内核级别或高度优化场景需要,但在应用软件上是完全不需要的。按现在软件质量看,它实现得非常野生,这样的能力只在能懂且能控制的人的设计中有用。对较大规模软件工程,就是灾难。)

如果某语言可以比较视觉优雅实现这个函数,则该语言函数须是一等公民(可以作为返回值),其次在闭包作用域实现上借鉴了 lisp。

让我来扩充现代语言的实现,比如 ES 的语法糖:

const foo = n => i => n + i;

Rust 显得格外严谨也显得强约束,其次整数类型 i32 还需要仔细斟酌:

fn foo(n: i32) -> Box<dyn Fn(i32) -> i32> {
    Box::new(move |i| n + i)
}

其实,能优雅实现的不只 lisp 和 lisp 方言、ruby 等,连前辈 tcl 都优雅:

proc foo {n} {
    return [lambda {i} {expr {$n} + $i}]
}

唯独 Python 的实现要丑陋一点,原因是 lambda 匿名函数的不支持 return,要不,支持象牙符后的 Python,以下代码也确实合理:

def foo(n):
    return lambda i : return (n := n + i)

但现在,较恰当的实现如下,nonlocal 在内部作用域捕获外部 n,也是合理的,从代码清晰性上说,python 更甚一筹:

def foo(n):
    def bar(i):
        nonlocal n
        return (n := n + i)
    return bar

第二个实现,属于老油条代码,这个程序员至少理解引用:

def foo(n):
    s = [n]
    def bar(i):
        s[0] += i
        return s[0]
    return bar

但这种属于一种 hack 方式绕过语法的不支持,不深入研究一下 Python 特性无法一下看懂。

累加器问题外延

累加器问题外延其实是技术竞争优势,对强抽象表达的编程语言带来的软件质量,这属于高智商的智力游戏。

事出一个精通 lisp 的人选择使用 lisp 开发商业应用,并利用 lisp 语言优势快速开发了 Web 程序。

这一切都是开创性的,我非常理解这个过程,这意味着没有轮子,大部分需要自己造。

所有的编程都是开拓性的,而不是使用已有的成熟方案,lisp 正是这种 10X 程序员的秘密武器。

然后,这个创业过程还胜出了,这其中发挥作用的,我看到的,是真正的批判性思维,和基于事实逻辑和编程实力。

它提到的 lisp 的表达和抽象能力能帮助他以更少的代码击败那些 java 公司。

或许在他那个时代是这样的吧,lisp 是他的秘密武器,人才市场中,lisp 看似人少,但创业团队 10 人以下就够,且懂 lisp 的都是精英,你在 lisp 圈子中很容易找到这 10 个懂 lisp 的人。

我非常喜欢“拒绝平庸”、“书呆子的复仇”两章,也喜欢 lisp,但这个语言放在国内用于创业,基本是失败了一半。

一是找不到真正理解 lisp 的精英用它来配合现有基础设施开发商业应用,二是这个年代开发技术,特别是编程语言技术的差异,已经无法构成竞争优势。

但没关系,这不妨碍它成为书呆子的快乐。

c++ 一直在,perl 之后 java 崛起了,然后 ror 颠覆了 web 领域编程范式,然后是 go,也许 rust 之后,再没有像样的颠覆者了,dojo 可认为只是在某领域的优化,尤其是看到 ror 在 rust 中的实现 loco 时,似乎编程语言走向了终结。

总的感觉

即使 lisp 在语言能力上顶级的,但并不意味着 lisp 和商业成功之间有强关联性,而是使用 lisp 的背后的人的思维能力导致的。

用中国话说就是“艺高人胆大”,选择了少有人走到路,路还走通了,只不过这个艺,并不仅仅指 Paul 精通 lisp,而是指他习得的思维方式与精神。

思考方式和背后的精神;前者能给特立独行的创业者在混沌的商业环境之中获得优势分,后者确保思考方式。

至于技术能力和思考能力,那可以在事上练就。