第 5 章
REPL里的手艺人
2011年秋天,北卡罗来纳州达勒姆的一家酒店会议厅里,Stuart Halloway站在投影屏幕前。他打开了一个终端窗口,启动了一个Clojure REPL会话。屏幕上只有一个提示符:user=>。他没有打开幻灯片,没有展示架构图,也没有列出演讲提纲。他开始在观众面前实时编写代码,每次只写一个表达式,运行它,观察返回值,然后基于这个返回值编写下一个表达式。他偶尔会犯错——输入错误的参数,忘记某个函数的返回类型——然后立即在REPL中检查,纠正,继续前进。整个过程的节奏类似于某种仪式:输入,回车,读取输出,思考,再次输入。房间里大约两百名开发者注视着这个过程,不是注视幻灯片上的抽象概念,而是注视一个思维过程在屏幕上成型。这场演讲后来成为Clojure社区中被反复提及的场景之一。不是因为内容特别深奥——Halloway演示的是如何用Clojure构建一个简单的数据管道——而是因为它展示了一种特定的工作方式。
REPL不是事后验证的工具,不是在编辑器里写完代码再运行的测试环境。REPL是思维本身的外部化,是与语言对话的媒介。把时间拨回更早的时候。2008年,Clojure的第一个公开邮件列表刚刚建立,早期的订阅者只有几十人。这些人大多是经验丰富的Java程序员或Lisp爱好者,他们带着各自的编程习惯和认知框架进入这个新语言的讨论。在最初的几个月里,邮件列表上的问题模式相当典型:有人问如何实现某个功能,有人报告遇到的错误,有人建议语法改进。但回答的方式开始呈现出一种特殊的倾向。一个典型的场景是这样的:某位开发者发帖询问如何在Clojure中处理嵌套的数据结构,描述了他想要达到的结果,并附上了一段他尝试编写的代码。这段代码通常带有明显的Java或Common Lisp风格——可能是循环嵌套,可能是显式的索引操作,可能是对可变状态的依赖。如果这个问题出现在其他语言的社区论坛上,常见的回答方式是给出正确的代码示例,或者指出文档中的相关章节。
但在Clojure邮件列表上,资深开发者们的回答往往采取了另一种形式。他们不会直接给出最终答案。相反,他们会回复一系列可以在REPL中逐步执行的表达式。提问者被邀请在自己的REPL中逐行输入这些表达式,观察每一步的返回值,理解数据如何被转换,函数如何组合。回答者提供的不是答案,而是一个可以复现的探索过程。这种模式在2008年到2010年间反复出现,逐渐沉淀为社区的一种不成文规范。它的运作逻辑值得仔细审视。当一位资深开发者用REPL序列回答问题,而不是给出最终代码时,他实际上在做几件事情:第一,他示范了如何思考Clojure程序——不是从整体设计出发,而是从具体的数据和转换出发;第二,他邀请提问者进入一个共同的认知空间——REPL会话是可复现的,任何人输入相同的表达式都会得到相同的结果;第三,他隐性地拒绝了权威论述的地位——回答的说服力不来自回答者的身份或声望,而来自提问者自己在REPL中观察到的行为。这第三点尤其关键。
在传统的技术知识传递中,文档、书籍和权威人物的解释构成了主要的权威来源。学习者接受信息是因为来源可靠。但在REPL驱动的知识传递中,权威被转移到了运行时行为本身。代码运行的结果是最终的仲裁者,回答者只是这个仲裁过程的引导者,而不是权威本身。2009年出版的《Programming Clojure》一书将这种工作方式系统化为教学策略。Stuart Halloway在书中没有按照传统的编程书籍结构来组织内容——即先介绍语法规则,再给出示例程序。相反,他从第一章开始就引入了REPL的使用,并且在每一章的讲解中都穿插着REPL会话的转录。读者被期望在阅读的同时打开REPL,输入书中的代码,观察结果,进行修改。这本书的第二章有一段明确的说明:读者应该在打开REPL的状态下阅读,书中的代码示例都设计为可以在REPL中逐段输入和测试,不要只是阅读它们——执行它们。这个要求在其他编程书籍中并不常见。
大多数技术书籍将代码示例作为插图和参考,读者可以选择是否实际运行它们。但在Halloway的教学设计中,REPL交互不是可选的辅助手段,而是理解语言的核心路径。这种设计背后有一套认识论立场。如果编程知识本质上是对语言行为的理解——知道某个函数在给定某些输入时会产生什么输出——那么最直接的知识获取方式就是观察语言行为本身。文档描述语言行为,但描述可能过时、不完整或不准确。书籍解释语言行为,但解释可能带有作者的偏见或盲点。只有REPL中的实际执行结果是不可争辩的,它是语言的直接显现。这套立场在Clojure社区中逐渐被内化。到了2011年前后,邮件列表上出现了一种新的回答格式:回答者不仅提供REPL序列,还会在序列中加入注释,标记出每一步的目的和观察要点。这种格式后来被称为“REPL叙事”——它既不是纯代码,也不是纯解释,而是一种混合体,代码的每一步执行都伴随着简要的说明,说明和代码交替推进,形成一种可执行的论证结构。
一个2011年3月的邮件列表帖子明这种格式的成熟程度。提问者询问如何用Clojure处理XML数据,这是一个常见但容易让新手困惑的任务。回答者Rich Hickey本人没有给出一个完整的XML处理函数,而是提供了一段REPL叙事:首先引入XML解析库,解析一个简单的XML字符串,观察返回值的结构,然后逐步提取嵌套的内容。Hickey没有解释XML解析的理论,没有讨论不同解析策略的优劣,没有给出一个封装好的工具函数。他只是在REPL中逐步操作,让数据结构的形状在每一步显现出来,让提问者自己看到嵌套的map是如何对应XML的层级关系的。这个过程中的每一步都是可验证的——提问者可以在自己的机器上输入相同的表达式,得到相同的结果。说服力完全来自可复现性。这种知识传递方式与传统的文档传递形成了鲜明的对比。
第四章讨论过Clojure社区文档生态的结构性困境——志愿者精力的供给无法匹配新人数量增长,文档项目起起落落,知识传递的循环面临松动甚至萎缩的压力。但REPL实践提供了一条不同的路径,一条绕过文档困境的路径。当文档匮乏时,社区发展出了一套面对面(或屏幕对屏幕)的知识传递仪式。资深开发者不写文档,但他们在邮件列表上写REPL叙事;他们不维护教程网站,但他们在会议演讲中做现场REPL演示;他们不出版教科书(除了少数几本),但他们在代码审查中逐行引导新手。这种实践形成了一种隐性知识的传递网络,它的运作不依赖于完善的文档基础设施,而依赖于实践共同体中手艺人的直接传授。手艺这个概念在这里不是比喻。在人类学对传统手工艺的研究中,手艺知识的传递有几个特征:它发生在实践过程中,而不是在课堂讲授中;它依赖于师傅的示范和徒弟的模仿;它的标准是“做得好”而不是“说得对”;它的权威来自作品的质量而不是师傅的身份。
Clojure社区的REPL实践具备了所有这些特征。当一位资深开发者在邮件列表上写下一段REPL叙事时,他是在进行一种示范。当一位新手在自己的机器上逐行输入那些表达式时,他是在进行模仿。当一位会议演讲者在舞台上实时构建程序时,他是在公开展示手艺的标准——什么样的代码节奏是好的,如何处理错误,如何从简单的表达式组合出复杂的功能。这些都不是通过阅读文档可以学到的,它们必须通过参与实践共同体来获取。2012年的Clojure/West会议上,一位名叫Alan Dipert的开发者的演讲将这种表演性推向了极致。他的演讲题目是关于ClojureScript的REPL集成,但他没有准备任何幻灯片。他打开了一个浏览器窗口和一个编辑器窗口,然后开始在现场观众面前构建一个互动式的网页应用。整个过程持续了四十五分钟,期间他遇到了三次错误——一次是拼写错误,一次是忘记引入某个命名空间,一次是数据结构不匹配。
每次错误发生时,他都在REPL中检查状态,找到问题,修复它,然后继续。这场演讲的视频后来在社区中广泛传播。有意思的是,评论中最常被提及的不是最终的应用功能,而是Dipert处理错误的方式。一位评论者写道,看到他犯错然后修复的过程比看到完美的演示更有教育意义,那让他觉得自己也可以做到。另一位评论者说,他终于理解了为什么他们总是说要在REPL中开发——不是因为那样更快,而是因为那样让你和你的程序保持对话。“对话”这个词频繁出现在社区成员描述REPL体验的语言中。这不是偶然的。REPL的首字母缩写本身就包含了“Loop”(循环)这个词——读取、求值、打印、循环。但这个循环不是机器的独白,而是人与语言之间的往返交流。程序员输入一个表达式(这是人的发言),语言返回一个值(这是语言的回应)。程序员阅读这个值,思考,然后输入下一个表达式。这个过程的结构确实类似于对话:每一次输入都基于上一次输出的理解,每一步都受到上一步结果的约束和引导。
这种对话式的开发方式塑造了一种特定的认识论偏好。在REPL驱动的开发中,程序员不会在开始编码之前花费大量时间进行整体设计。相反,他们从一小块具体的功能开始——一个数据结构,一个转换函数——在REPL中验证它,然后基于验证结果决定下一步。整个程序不是作为蓝图一次性构建的,而是作为一系列小步骤逐步生长的。这种偏好深刻地影响了Clojure社区对“好的开发方式”的共识。在邮件列表讨论中,当一个争议出现时——比如是否应该引入某个新特性,或者某个库的设计是否合理——最有力的论证往往不是抽象的原则阐述,而是可执行的代码示例。一个能够在REPL中运行并展示问题所在(或解决方案)的代码片段,比一千字的论述更有说服力。这里存在着一种认识论上的位移:从“谁说的”转向“什么能运行”。在传统的权威结构中,决策的依据往往是发言者的身份、经验或声望。但在REPL驱动的社区中,代码的运行结果成为了最终的仲裁者。
这不是说身份和经验不重要——资深开发者的意见仍然更有分量——而是说他们的意见必须能够通过REPL的验证来支撑。一个不能在REPL中复现的主张,无论来自谁,都很容易被质疑。这种认识论偏好的形成有其历史条件。Clojure诞生于2007年,那时现代软件开发的工具链已经相当成熟。单元测试框架、持续集成系统、版本控制工具都已经广泛使用。但Clojure社区没有简单地将这些工具纳入现有的工作流,而是以REPL为中心重新组织了开发实践。在其他语言中,REPL通常是一个辅助工具,用于快速试验小段代码或调试。在Clojure中,REPL成为了开发过程的核心引擎,编辑器、测试框架和构建工具都围绕着它配置。这种以REPL为中心的工作方式并非Rich Hickey在语言设计时强制规定的。Clojure的语言规范中没有要求开发者必须使用REPL驱动的开发方式。
但语言的某些设计选择使得这种方式特别自然和高效:不可变数据结构意味着每一步操作都返回新的值而不是修改现有值,这使得在REPL中逐步构建数据转换链变得安全;函数作为一等公民意味着可以随时定义和重新定义函数,无需重启REPL;与Java的无缝互操作意味着可以直接在REPL中调用任何Java库,观察其行为。这些设计选择共同创造了一种环境,在这个环境中,REPL驱动的开发不是一种额外的工作量,而是一种自然的工作流。开发者不需要刻意采用这种方法论;他们只需要打开REPL开始工作,就会发现自己正在这样做。但这里也存在一个问题,一个贯穿本章始终的隐性问题:如果REPL实践是一种手艺知识传递的方式,那么它的可扩展性如何?手艺传递依赖于师傅与徒弟的直接接触,依赖于示范和模仿的近距离观察。在一个只有几十人或几百人的小社区中,这种模式运作良好。但当社区增长到几千人、几万人时,当新人涌入的速度超过了资深开发者能够亲自引导的速度时,会发生什么?
这就是第四章结尾留下的压力点——当指路者的精力供给无法匹配新人数量增长时,依赖志愿者精力的知识传递循环是否会开始松动甚至萎缩——在REPL实践中的具体投射。文档可以规模化分发,一本书可以卖给成千上万的读者,一个教程网站可以服务任意数量的访问者。但REPL叙事是一对一的或一对少的:它需要一位资深开发者在某个具体时刻,回应某个具体的问题,写下一段具体的代码序列。这种模式在本质上不具有规模经济。2013年的社区调查数据提供了一个间接的视角。那一年对1,060名受访者的调查发现,47%的受访者在使用Clojure的同时也使用ClojureScript。这个数字在2014年增长到55%,2015年达到66%(基于2,445名受访者)。社区在增长,新人在涌入,但邮件列表上的回答者数量并没有同比例增长。一些早期的活跃回答者开始减少发言频率,部分是因为工作压力,部分是因为问题模式的重复让他们感到疲惫。
2014年夏天的一个邮件列表帖子捕捉到了这种紧张关系的一个瞬间。一位新手发帖询问一个关于惰性序列的问题,这是一个在Clojure社区中被反复讨论过无数次的话题。几分钟后,一位资深开发者回复了一个链接,指向2009年的一个邮件列表存档,那里有几乎相同的问题和详细的REPL叙事式回答。这位资深开发者没有重新写一遍REPL叙事,他只是指向了过去的存档。这个行为本身是合理的——为什么要重复已经做过的工作?但它也揭示了REPL实践作为知识传递机制的一个局限:REPL叙事存在于邮件列表的存档中,但存档中的REPL叙事是静态的、去语境化的。它缺少了现场互动中的引导节奏,缺少了提问者可以根据自己的理解进度来调整步骤顺序的灵活性,缺少了那种在对话中学习的临场感。存档中的REPL叙事变成了另一种形式的文档——它不再是手艺人的现场示范,而是一份记录,一份过去的痕迹。
从2014年开始,社区中出现了一种新的实践:将邮件列表中的经典REPL叙事整理成博客文章或wiki页面。这是一种试图将手艺知识文档化的努力,试图在不丧失REPL叙事的可执行性的前提下,赋予它一定的持久性和可发现性。但这种转化不可避免地改变了知识的性质。一篇博客文章中的REPL序列失去了对话的语境——它不再是回应一个具体问题的具体回答,而是一个通用的教程。它从手艺人的现场示范变成了教学材料。这种转化是好是坏?从知识传播的效率来看,它显然是好的——一篇博客文章可以被成千上万的人阅读,而一封邮件列表回复可能只被几十人看到。但从手艺知识传递的质量来看,它有所损失——读者失去了与师傅直接互动的机会,失去了那种通过模仿和即时反馈来内化标准的体验。2015年,一个名为“Clojure REPL Style Guide”的非正式文档开始在社区中流传。
它不是官方发布的,不是任何核心团队成员编写的,而是由几位社区成员收集了邮件列表和会议演讲中的REPL实践模式,整理成的一份风格指南。这份指南没有强制力,但它记录了社区中已经形成的共识:在REPL中使用有意义的变量名以便后续引用;将复杂的表达式拆分成多步以便检查中间结果;使用doc和source函数来查看文档和源码而不是切换到浏览器;在REPL中保留探索的历史以便回溯和分享。这份风格指南的存在本身就是一个有趣的现象。它不是关于语法的——语法是由语言规范定义的。它不是关于API的——API是由库的作者定义的。它是关于工作方式的——而工作方式是由社区在实践中形成的共识定义的。这份指南试图捕捉一种隐性的手艺标准,把它变成显性的文字建议。但它的作者们很小心地避免让它变成教条。
指南的开头声明这些不是规则,而是社区中许多开发者发现有效的实践模式,它们在大多数情况下有效,但在某些情况下可能不适用,最好的判断标准永远是开发者在自己的REPL中观察到的东西。最后这个判断——“最好的判断标准永远是你在自己的REPL中观察到的东西”——是整个手艺共识的基石。它把权威从指南本身转移到了开发者的个人经验上。指南只是建议,REPL才是最终的仲裁者。这种态度贯穿了Clojure社区的各个层面:语言设计争议中,Rich Hickey经常用REPL演示来说明为什么某个提议的特性会导致不一致的行为;库的设计讨论中,作者经常用REPL会话来展示不同设计选择的后果;代码审查中,审查者经常用REPL序列来指出潜在的问题而不是仅仅描述它们。手艺共识就是这样建立起来的:不是通过投票,不是通过权威声明,而是通过成千上万个REPL会话中积累的共同经验。
当足够多的开发者在他们的REPL中观察到相同的模式——某些写法总是导致混乱,某些组合总是清晰有效,某些抽象层次总是恰到好处——一种关于“好代码”的共同直觉就形成了。这种直觉不需要被写进规范文件,它在每一次REPL交互中被重新确认和传递。但这种共识也有其脆弱性。它依赖于实践共同体的持续性——如果社区的核心成员大规模离开,如果新人的涌入速度超过了手艺传递的速度,如果替代性的开发方式(比如更传统的编辑-编译-测试循环)开始流行,那么REPL驱动的手艺共识就可能被稀释甚至中断。这不是一个假设性的担忧。到2015年底,Clojure社区已经不再是一个小型的、同质化的群体。它包括了来自不同编程背景的开发者——有些人来自Java企业开发,有些人来自Ruby on Rails的快速原型文化,有些人来自JavaScript的前端世界,有些人来自学术性的函数式编程传统。
这些不同的背景带来了不同的工作习惯和认知框架,它们不一定与REPL驱动的手艺传统相容。2016年初的一个事件可以作为这种紧张关系的缩影。一位来自JavaScript社区的开发者在邮件列表上发帖,建议Clojure应该有一个官方推荐的IDE配置,理由是并非所有人都有时间学习Emacs和使用REPL。这个建议在社区中引发了激烈的讨论。一些人认为这是一个合理的请求——降低入门门槛对社区的健康发展是必要的。另一些人则认为这误解了Clojure开发的基本性质——REPL不是一种编辑器偏好,而是与语言交互的核心方式,放弃REPL就是放弃了理解语言行为的最直接路径。这场讨论没有达成任何正式的结论——Clojure社区很少有事情是通过正式投票决定的。但它揭示了一个正在浮现的张力:随着社区的扩大和多样化,那些曾经在小群体中不言自明的手艺共识,开始受到来自不同传统的质疑和挑战。
那些曾经通过面对面示范传递的隐性知识,在面对大规模的新人涌入时,开始显得不够用。不过,这个问题在2016年还没有到达危机点。邮件列表上的REPL叙事仍然在继续,会议演讲中的现场演示仍然在进行,《Programming Clojure》仍然在教导新一代的开发者打开他们的REPL。手艺共识的传递机制虽然承受着压力,但仍然在运转。真正的问题还没有被充分意识到:当社区继续增长,当新人的编程背景更加多样化,当商业公司开始采用Clojure并带来企业级的流程要求时,这套基于手艺人直接传授的知识体系还能维持多久?这个问题在当时没有人能够回答。但它的轮廓已经开始在社区实践的边缘显现。REPL会话中的每一次输入和输出都在重申一种认知契约——真相不在文档里,不在权威的论述里,而在键盘与屏幕之间那个可复现的瞬间里。这份契约在小规模的手艺共同体中运转自如,但当共同体的边界不断扩展,当新来者带着不同的认知契约进入这个空间时,维持这份契约的成本就开始上升。
这种压力并非Clojure独有。其他语言社区也面临过类似的规模与手艺传递之间的张力。例如,Elm语言在2012年由Evan Czaplicki作为毕业论文创建,其早期发展同样依赖于小范围内的紧密互动和在线编辑器的即时反馈。Elm的首次发行带有很多例子和一个在线编辑器,使得易于在web浏览器中试验它。Evan在2013年加入Prezi从事Elm的工作,并在2016年转移到NoRedInk作为开源工程师,启动了Elm软件基金会。Elm社区也强调REPL和在线编辑器作为学习和验证的工具,但随着用户增长,同样面临如何将核心实践传递给更多人的挑战。这种跨社区的相似性表明,手艺共识的规模压力是函数式编程社区在成长过程中普遍遭遇的结构性问题。
成本的上升不是戏剧性的,它表现为更多的重复问题、更频繁的存档链接回复、更少的新手能够独立穿越那片信息丛林。手艺的传递机制在压力下仍然在工作,但它能承受的压力是有上限的——这个上限,在2016年初春的邮件列表讨论中,第一次变得隐约可见。