Abstract Heresies

关于计算机科学和编程的非正统观点。

2025年4月10日 星期四

为什么我选择用 Lisp 编程

Lisp 并不是最流行的语言。它从来都不是。其他通用编程语言比 Lisp 更受欢迎,并且最终可以完成 Lisp 可以完成的一切(如果 ChurchTuring 是正确的)。它们拥有比 Lisp 更多的库和更大的用户社区。它们也更有可能被安装在机器上。

然而,我更喜欢用 Lisp 编程。我始终保持一个 Lisp REPL 处于打开状态,并且我用 Lisp 编写原型和探索性代码。我为什么要这样做呢?Lisp 更容易记忆,限制更少,需要跳过的障碍也更少,我的想法和我的程序之间的“摩擦”更小,它很容易定制,而且坦率地说,更有趣。

Lisp 那令人恐惧的剑桥波兰表示法是统一和通用的。我不必记住一个形式是否需要花括号或方括号,或者运算符的优先级是什么,或者一些毫无理由发明的奇怪标点语法。它的形式是 (operator operands ...),适用于一切。没什么需要记住的。我基本上在 40 年前就停止注意到括号了。我可以随意缩进。

我主要进行函数式编程,而 Lisp 具有三个在这方面非常有帮助的特性。首先,如果你避免副作用,它会直接支持替换模型。你可以告诉 Lisp,当它看到这个简单的形式时,它可以直接用那个更复杂的形式替换它。Lisp 不会不断地把你推向命令式思维。其次,由于语法是统一的,并且不依赖于上下文,你可以随意重构和移动代码。只需移动平衡的括号中的内容,你基本上就可以搞定。

第三,在大多数计算机语言中,你可以通过用一个命名值的变量来替换一个特定的值来抽象出一个特定的值。但是你可以通过用一个计算值的函数来替换一个命名数量的变量来进行进一步的抽象。在函数式编程中,你经常淡化一个值和一个产生该值的函数之间的区别。毕竟,区别只在于等待答案的时间。在 Lisp 中,你可以通过简单地用一个 lambda 表达式包裹它,将一个表示对象的表达式转换为一个计算对象的抽象。这些天这已经不是什么大不了的事了,但是正确工作的 lambda 表达式直到最近才在 Lisp 中可用。即便如此,lambda 表达式在其他语言中通常也很笨拙。

函数式编程侧重于函数(可想而知!)。这些是理想的黑盒抽象:值进入,答案出来。里面发生了什么?谁知道!谁在乎!但是你可以将小的简单函数连接在一起,得到更大更复杂的函数。这样做没有限制。如果你可以将你的问题框定为“我拥有这个,我想要那个”,那么你可以将其编码为函数式程序。诚然,函数式编程需要一些练习才能适应,但它允许你用非常简单的部分构建复杂的系统。一旦你掌握了它,你就会开始将一切都视为一个函数。(这不是限制。Churchlambda 演算是一种基于函数组合的计算模型。)

Lisp 让我能够以我能想到的最快的速度尝试新的想法。新程序与语言内置的程序没有区别,所以我可以像构建它们一样轻松地构建在它们之上。Lisp 的调试器意味着我不必停止一切并从头开始重新启动世界,每次出现问题时都不用这样。Lisp 的安全内存模型意味着错误不会在我探索问题时破坏我的工作区。

Lisp 中的 REPL 评估 表达式,这些表达式是 Lisp 程序的基本组成部分。你可以输入 Lisp 程序的一部分,并立即看到它的作用。如果它有效,你可以简单地将该表达式嵌入到更大的程序中。你的程序随着你探索问题而实时成形。

Lisp 的动态类型为你提供了几乎自动的临时多态性。如果你编写一个调用 + 的程序,它将作用于任何具有明确定义的 + 运算符的对象对。现在,如果你对你的类型掉以轻心,这可能是一个问题,但是如果你稍加约束(例如,不在字符串和数字的组合上定义 +),并且如果你避免自动类型强制转换,那么你可以编写非常通用的代码,该代码可以作用于你的数据类型的超集。(动态类型是一把双刃剑。它允许快速原型设计,但它可以隐藏在静态类型语言中会在编译时被捕获的错误。)

其他语言可能共享其中的一些特性,但 Lisp 将它们全部结合在一起。它是一种被设计成用作思考问题的工具的语言,而那是编程的乐趣所在。