LISP和它的REPL(修改中)
主题
推行Lisp大概是件困难的事情吧, 毕竟62年都过去了, Clojure(最流行的lisp)还在语言排行榜20名以外徘徊.
Lisp有两大谜团, 一是从来不主流但是有旺盛的生命力, 二是有旺盛的生命力但是从来不主流.
1950年代,他们说COBOL是为未来的语言.
1960年代,Smalltalk被视为未来的语言.
1980年代,Pascal、Delphi、C和C++竞争谁是未来的语言.
1990年代,Java被视为未来的语言,还有他的私生子C#.
2000年代,JavaScript被视为未来的语言, Ruby也是.
2010年代, 移动时代的到来带来了一波Objective C的复兴.
2020年代, Python也迎来了自己的时代, Rust, Zig等一众群魔尚在乱舞.
而lisp在以上每一个年代留下了自己的痕迹.
如果'某些'程序员对于Lisp的喜爱不仅仅是出于年龄的偏执, 对故弄玄虚的渴望, 或者对魔鬼的崇拜, 或者干脆说, 这份爱还那么一丝理性的话.
我得尝试把理性的部分说清楚. 导向到一个结论.
结论
Lisp是帮助我们思考工具. 因为它的编程模式富有生命力, 具有强大的可交互性和可见性.
工具
"君子性非异也,善假于物也".
-- 韩愈
在所有动物运动能耗效率, 某种动物的运动效率 = 热量消耗/体重/运动距离.
自然状态下:
羊 > 狗 > 牛 > 人
工具使用:
骑车的人 >> 羊 > 狗 > 牛 > 人
自行车是个很好的比喻, 年轻的乔布斯对计算机的愿景, 是人类思维的自行车.
如果手机也算是计算机的话, 自行车最终变成轮椅就是个格外悲伤的故事了.
不可想象的思想
Just as there are odors that dogs can smell and we cannot, as well as sounds that dogs can hear and we cannot, so too there are wavelengths of light we cannot see and flavors we cannot taste. Why then, given our brains wired the way they are, does the remark, "Perhaps there are thoughts we cannot think," surprise you? Evolution, so far, may possibly have blocked us from being able to think in some directions; there could be unthinkable thought.
-- Richard Hamming
Dr. Hamming(哈名校验码)是想表明, 我们的认知是有局限的, 视觉, 听觉甚至是思考, 莫不如此. 工具能扩展我们的认知能力. 对于看不见的光, 听不到的声音, 现代科学已经给出了答案.
unthinkable thought... 想不到的想法, 我怎么描述我想都想不到的想法呢?
举个例子, 生命在底层纯数字式的, DNA由AGTC四种碱基编码构成. 记述了所有的蛋白质合成方法, 能够操作一个受精卵到完整人体的全部过程.
代码量: 750M, 大概一张CD光盘信息量, windowsxp代码都比这个多.
数据类型, 对象, 常量, 有意义的命名, 作用域, 管道, 接口, 所有我们在编程中珍视的概念, 都是无用的信息冗余. 它们之所以存在, 是为了填补我们可笑的理解能力和真实世界的复杂性之间的鸿沟.
如果有神的话, 这是神的手笔, 虽然我们不能理解神, 但这并不妨碍我们赞美神的伟绩!
编程的时候monkey patching就已经很hacky了, 想象一下:迄今我们只能用删除法来debug这些代码--干掉一部分代码看看会发生什么, 很多时候的结果都出乎意料. 这些代码:
- 环境敏感: 受精卵的第一次分裂为两个细胞的位置差异决定了哪个负责脑组织发育
- 并发执行: 人体的10万种蛋白质同步合成需要DNA代码的多处同步执行
- meta-programming: 蛋白质具体参与生化过程, 是氨基酸编码的, DNA仅仅是蛋白质的模版,这是一种怎样的meta-programming...
- 自洽自持: 一套编码, 可以具象为各种不同类型的细胞, 可以独立存在, 又可以以我们难以想象的复杂方式组织在一起, 形成完整的人体结构. 这一切的发生不依赖于某个中心环节的调度控制.
思考和思考的媒介
受益于以上机制, 我们很强大, 我们的认知和操控能力是亿万年自然进化的至高成就, 能够我们引导我们穿梭于复杂的丛林, 接住时速超过100的飞行物体(比如网球), 毫不费力的从人群中认出自己的朋友.
我们的认知能力又弱小的可怜, 连个11位的电话号码都记不住几个. 我记忆的巅峰是初中时代, 抄答案一眼能记住4组 ACBDA DABCC CDACB BBAAD. 就在写这段话的时候, 20年之后的我只能记两组, 也就是10个问题...
因为但抽象记忆和抽象计算的能力并非我们先天具备, 而是我们在和工具的互动中获得的. 我们的工具是我们和这个世界交互的方式, 任何的认知缺乏逻辑和抽象,对我们都是一团混沌, 我们需要用户界面去理解这个基于数理, 现代UI的几个要素, 不独存在于屏幕中, 是我们'使用'世界的方式.
- 可互动: 可以直观操作, 大小适中, 给我们一种可以握在手里, 抱得动, 拿得起的感觉, 回应我们的交互
- 可视化: 具象, 看得见.
- 符号化: 以此为基础可以继续组合, 抽象, 融入我们对世界的现有理解.
仔细想一下, 现代UI的设计儿童玩具的设计暗合, 玩具和玩耍是儿童认知这个世界的方式, 这绝非偶然.
人类的认知能力太过脆弱和挑剔, 我们要尽200%的努力去用合适的工具去适应,迎合它.
以DNA的发现为例子, 1962年James Watson, Francis Crick 因为发现了DNA的双螺旋结构而获得诺贝尔奖.
如何想象一个还不存在于认知中的分子结构? 下图中的铜板, 导线, 是他们思考的工具和媒介, 拼合, 拆分, 交互, 给这些东西起名字, 再拼合拆分, 这是Watson认识DNA世界的方式, 是他的玩具, 是他的REPL.

回顾人类的认知发展过程的一些例子:
- 书写(作为工具)使我们具有了符号抽象的能力, 是我们一切文明创造的载体.

- 数字符号(作为工具)为了我们提供了代数计算的能力. 历史上出现过多套数字符号, 中文一二三四, 罗马数字I II IV V, 但这些符号哪怕是加减都比阿拉伯数字复杂许多. 更不用说乘法和除法. 想象下怎么用汉字列个竖式...

- 数学符号(作为工具)使我们具有了理解数学世界的能力, 没有现代数学符号, 就不会有微积分, 不会有麦克斯韦方程

- 图表(作为工具)使我们赋予了我们理解大量数据, 抽取趋势的能力, 想象下看表格和看图标的区别.

我们是工具的创造者, 也是工具的使用者, 是工具的主人, 也是工具的奴隶.
思维必须借助媒介, 媒介的扩展, 是我们认知能力的扩展.
Lisp
以上我们谈到了
- 自行车
- DNA编码
- 人类的限制
- 认知工具
- 玩具
- 媒介
始终围绕一个主题: 我们思维的无力感和解决方案, 我们承认前者的存在, 并且给出了解决方案:合适的媒介/工具/抽象.
接下来, 我们说一下Lisp和Repl.
编程同人类以往任何智力活动一样, 需要工具的辅助. 编程是一个工具上极度受限的环境, 传统的创造性活动, 可以使用自己的五感:

现在只能盯着一个屏幕:

编程的工具必须在正确的抽象层次上辅助我们, 再次回到UI设计的基本原则:
- 可互动: 可以直观操作, 大小适中, 给我们一种可以握在手里, 抱得动, 拿得起的感觉, 回应我们的交互. 这一点上, 解释类语言比编译类语言强, Lisp则做到了极致.
- 可视化: 具象, 看得见. Lisp的抽象中, 只有atom和expression, 程序的执行方式是求职, 所有的值都对程序员可见.
- 符号化: 以此为基础可以继续组合, 抽象, 融入我们对世界的现有理解. Lisp是以expression的递归和嵌套作为作为程序组织的方式.
Lisp 借助 repl机制, 可以连接远端执行中的程序, 其中的的任意一段代代码均可以修改, 求值. 这才是互动, 分析日志和Coredumnp那是尸检...和活物打交道有趣多了.
Alan比我说的好:
“Pascal is for building pyramids—imposing, breathtaking, static structures built by armies pushing heavy blocks into place. Lisp is for building organisms—imposing, breathtaking, dynamic structures built by squads fitting fluctuating myriads of simpler organisms into place.”
-— Alan Perlis
真正理解了repl才算真正理解了lisp.
2011年的时候Steve Yegge给了Clojure一番差评, 对于一个新语言来说, 这个评价很致命.

其中关于编译对象的争论Rich Hicky在hacker news上给出了回复
Clojure, like many Lisps before it, does not have a strong notion of a compilation unit. Lisps were designed to receive a set of interactions/forms via a REPL, not to compile files/modules/programs etc. This means you can build up a Lisp program interactively in very small pieces, switching between namespaces as you go, etc. It is a very valuable part of the Lisp programming experience. It implies that you can stream fragments of Lisp programs as small as a single form over sockets, and have them be compiled and evaluated as they arrive. It implies that you can define a macro and immediately have the compiler incorporate it in the compilation of the next form, or evaluate some small section of an otherwise broken file. Etc, etc.
啥...Clojure 没有'编译单元'的概念?? Richy Hichy是疯掉了吧, 没有文件/模块/程序?? 接下来他做了解释:
Clojure代码文件编译的过程, 本质上和把文件中的内容逐行敲到repl上并没有什么区别. Clojure的编译对象是表达式. 一个clj文件就是一段顺序执行的脚本的记录
Repl并不是什么'随着clojure启动的调试器', repl就是Clojure, 从头到脚, 从脚到头.