Lisp在网页应用中的运用(2001)

Hacker News Top 新闻

摘要

Paul Graham结合自己创办Viaweb的经验,讨论了在网页应用中使用Lisp的优势,包括语言自由度、增量开发以及快速修复bug。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/05/19 22:05

来源:https://sep.turbifycdn.com/ty/cdn/paulgraham/bbnexcerpts.txt Lisp 在基于 Web 的应用中的使用 Paul Graham (这是2001年4月在马萨诸塞州剑桥市BBN实验室所做演讲的摘录。) **任何你喜欢的语言** 使用 Lisp 编写基于 Web 的应用的一个原因是,你*可以*使用 Lisp。当你编写只在自家服务器上运行的软件时,你可以使用任何你喜欢的语言。长期以来,程序员在编写应用程序时没有太多选择。直到最近,编写应用程序还意味着编写在桌面计算机上运行的软件。在桌面软件中,存在一种强烈的倾向,即使用与操作系统相同的语言来编写应用程序。十年前,实际上所有应用程序都是用 C 编写的。而在基于 Web 的应用中,情况发生了变化。你控制着服务器,可以用任何你喜欢的语言编写软件。你现在可以理所当然地认为,你拥有操作系统和编译器的源代码。如果语言和操作系统之间真的出现任何问题,你可以自己修复。 然而,这种新自由是一把双刃剑。拥有更多选择意味着你现在必须考虑该做出哪种选择。过去要简单得多。如果你负责一个软件项目,某个麻烦的人建议使用与你通常所用不同的语言来编写软件,你只需告诉他们这不切实际,事情就结束了。现在,对于基于服务器的应用,一切都变了。你在语言选择上现在受到市场力量的制约。如果你试图假装一切照旧,像我们大多数竞争对手那样只使用 C 和 C++,那你就是在自掘坟墓。一家使用更强大语言的小型初创公司会把你打得落花流水。 **增量开发** Lisp 有一种特定的软件开发风格。其传统之一是增量开发:你首先尽快编写一个几乎不做任何事的程序。然后逐步添加功能,但在每一步你都有可运行的代码。我认为这种方式能让你更快地写出更好的软件。Lisp 的一切都针对这种编程风格进行了优化,因为 Lisp 程序员至少已经以这种方式工作了三十年。Viaweb 编辑器可能是增量开发最极端的例子之一。它始于一个120行的程序,用于生成网站,这个程序是我在一本刚好在 Viaweb 启动前完成的书中的一个例子。Viaweb 编辑器最终增长到大约25,000行代码,就是从这个程序逐步演变而来的。我从未坐下来重写整个程序。我想我从未有过超过一两天没有可运行代码的情况。整个开发过程就是一系列渐进的修改。这种开发风格非常契合基于 Web 软件可能实现的滚动发布。通常来说,这也是更快编写软件的方法。 **交互式顶层** Lisp 的交互式顶层对于快速开发软件有很大帮助。但对我们来说,最大的优势可能在于发现 bug。正如我之前提到的,对于基于 Web 的应用,用户数据就在你的服务器上,通常可以重现 bug。当客户支持人员向我报告编辑器中的一个 bug 时,我会将代码加载到 Lisp 解释器中,并登录到用户的账户。如果我能重现这个 bug,就会进入一个实际的调试循环,明确告诉我哪里出了问题。通常我可以立即修复代码并发布修复。而当我说的“立即”,是指用户还正在通话中。如此快速的 bug 修复周转,让我们处于一个极具诱惑的位置。如果我们能在用户还在通话时捕捉并修复 bug,那么让用户以为这只是他们的错觉就非常诱人。所以我们有时(令他们高兴)会让客户支持人员告诉用户只需再次尝试登录,看看问题是否还存在。当然,当用户重新登录时,他们就会得到新发布的、已修复 bug 的软件版本,一切都会正常运行。我意识到我们有点狡猾,但这过程也确实很有趣。 **用于 HTML 的宏** Lisp 宏是我们的另一个巨大优势。在 Viaweb 编辑器中,我们非常广泛地使用宏。它可以被准确地描述为一个大型宏。这让你了解我们对 Lisp 的依赖程度,因为没有其他语言具备 Lisp 意义上的宏。我们使用宏的一种方式是用来生成 HTML。宏和 HTML 之间有一种非常自然的契合,因为 HTML 是一种像 Lisp 一样的前缀表示法,并且和 Lisp 一样是递归的。所以我们在宏调用内部嵌套宏调用,生成最复杂的 HTML,而这一切仍然非常可控。 **嵌入式语言** 宏的另一个重要用途是我们用来描述页面的嵌入式语言,称为 Rtml。(我们编造了各种关于 Rtml 代表什么的解释,但实际上我用 Viaweb 的另一位创始人罗伯特·莫里斯的名字命名,他的用户名是 Rtm。)我们软件生成的每个页面都是由 Rtml 编写的程序生成的。我们称这些程序为模板,以降低其吓人程度,但它们是真正的程序。事实上,它们是 Lisp 程序。Rtml 是宏与内置 Lisp 操作符的组合。用户可以编写自己的 Rtml 模板来描述他们想要的页面外观。我们有一个用于操作这些模板的结构编辑器,很像 Interlisp 中的结构编辑器。用户无需输入自由格式的文本,而是通过剪切和粘贴代码片段来组合。这意味着不可能出现语法错误。这也意味着我们不需要显示底层 s-表达式中的括号:我们可以通过缩进来展示结构。通过这种方式,我们使该语言看起来不那么吓人。我们还设计了 Rtml,使其在运行时不会出现错误:每个 Rtml 程序都会生成某种网页,你可以通过不断调试直到它生成你想要的页面。 最初我们预计我们的用户会是 Web 顾问,并希望他们大量使用 Rtml。我们提供了一些默认模板(如分类页面和商品页面等),目的是让用户能够使用并修改它们,以制作他们想要的任何页面。但事实是 Web 顾问并不喜欢 Viaweb。一般来说,顾问喜欢使用那些对客户来说太难用的产品,因为这能保证他们的持续雇用。顾问们会来到我们的网站,网站到处宣传我们的软件非常易于使用,任何人都可以在五分钟内创建一个在线商店,他们会说,我们绝不会用那个。所以我们并没有得到 Web 顾问的太多关注。相反,用户往往是最终用户,也就是实际的商家。他们喜欢能够自主控制自己网站的想法。而这种用户不想进行任何编程。他们只使用默认模板。所以 Rtml 最终并非程序的主要界面。它最终扮演了两个角色。首先,它是为那些真正高级的用户提供的一个逃生舱,这些用户想要一些我们内置模板无法提供的东西。在 Viaweb 的发展过程中,有人给了我一个非常有用的建议:用户总是想要一个升级路径,尽管通常他们永远不会走这条路。Rtml 就是我们的升级路径。如果你想,你可以完全控制页面上的所有内容。每几百个用户中只有一个人会编写自己的模板。这引出了 Rtml 的第二个优势。通过观察这些用户如何修改我们的内置模板,我们知道了需要添加什么。最终我们设定了一个目标:没有人需要再使用 Rtml。我们的内置模板应该能满足人们的所有需求。在这种新方法中,Rtml 对我们来说是一个警告信号,表明我们的软件缺少了什么。 使用 Rtml 的第三个也是最大的好处是我们自己从中获得的好处。即使只有我们使用 Rtml,以这种方式编写软件也非常值得。在软件中拥有这一额外的抽象层,使我们比竞争对手具有巨大优势。首先,它使我们的软件设计更加清晰。我们不像竞争对手那样只有生成网页的实际 C 或 Perl 代码片段,而是拥有一种用于生成网页的高级语言,网页样式用该语言指定。这使得代码更加清晰,更易于修改。我已经提到基于 Web 的应用是作为一系列小修改发布的。当你这样做时,你需要能够知道任何给定修改的严重程度。通过将代码分层,你可以更好地掌握这一点。修改底层(Rtml 本身)是一件需要深思熟虑、很少进行的重要事情。而修改顶层(模板代码)则是可以快速完成的事情,无需过多担心后果。Rtml 是一个非常 Lisp 化的做法。它首先主要是 Lisp 宏。在线编辑器在幕后操作的是 s-表达式。当人们运行模板时,它们会在运行时通过调用 compile 被编译成 Lisp 函数。Rtml 甚至严重依赖关键字参数,而在此之前我一直认为这是 Common Lisp 中比较可疑的特性之一。 由于基于 Web 软件的发布方式,你必须设计软件使其易于更改。Rtml 本身也必须像软件的其他部分一样易于更改。Rtml 中的大多数操作符都设计为接受关键字参数,事实证明这非常有用。如果我想为某个操作符的行为增加一个维度,只需添加一个新的关键字参数,所有用户的现有模板都能继续工作。少数 Rtml 操作符没有使用关键字参数,因为我认为永远不需要更改它们,但几乎每一个后来都让我后悔不已。如果我能回到过去从头开始,我会做的一件事就是让每个 Rtml 操作符都接受关键字参数。 事实上,我们在编辑器中有几个嵌入式语言。另一个我们没有直接暴露给用户的,是用于描述图像的语言。Viaweb 包含一个用 C 编写的图像生成器,它可以接收图像的描述,创建该图像,并返回其 URL。我们也用 s-表达式来描述这些图像。 **闭包模拟子程序** 将网页作为用户界面的问题之一,是 Web 会话本质上无状态。我们通过使用词法闭包来模拟类似子程序的行为来绕过这个问题。如果你了解延续,我们可以解释为我们的软件是用延续传递风格编写的。当大多数基于 Web 的软件在页面上生成链接时,它往往在想:如果用户点击这个链接,我想用这些参数调用这个 CGI 脚本。而当我们的软件生成一个链接时,它可以想:如果用户点击这个链接,我想运行这段代码。而这段代码可以是任意代码,可能(实际上通常是)包含来自周围上下文的自由变量。我们实现这一点的方法是编写一个宏,它接受一个预期为闭包的初始参数,后面跟着代码体。然后该代码会被存储在一个全局哈希表中,并赋予一个唯一 ID,而代码体生成的任何输出都会出现在一个链接内,该链接的 URL 包含这个哈希键。如果用户接下来点击了这个链接,我们的软件就会找到并调用相应的代码片段,链条继续。实际上,我们是动态生成 CGI 脚本,只不过它们是能够引用周围上下文的闭包。 目前为止这听起来很理论化,让我给你一个例子,说明这种技术带来的明显差异。在基于 Web 的应用中,你经常需要编辑一个具有各种类型属性的对象。对象的许多属性可以表示为表单字段或菜单。例如,如果你正在编辑一个表示人的对象,你可能有一个用于姓名的字段,一个用于头衔的菜单选择,等等。现在,如果某个对象有一个属性是颜色,会发生什么?如果你使用普通的 CGI 脚本,所有操作都必须在一个表单上完成,底部有一个“更新”按钮,那你会遇到麻烦。你可以使用文本字段让用户输入 RGB 数值,但最终用户不喜欢这样。或者你可以提供一个可选颜色的菜单,但那样你又不得不限制颜色范围,否则即使只提供标准的 Web 颜色映射表,你也需要256个菜单项,这些选项的名称几乎难以区分。 在 Viaweb 中,我们能够做的是将颜色显示为表示当前值的色块,后面跟着一个“更改”按钮。如果用户点击“更改”按钮,他们就会进入一个带有颜色图像映射的页面进行选择。在选择颜色后,他们会返回到编辑对象属性的页面,并且颜色已经更改。这就是我所说的模拟子程序行为的意思。软件的行为就像是刚刚从选择颜色的操作中返回。当然,实际情况并非如此;它是在进行一次新的 CGI 调用,看起来像是从堆栈中返回。但通过使用闭包,我们可以让用户和我们自己都感觉像是在进行子程序调用。我们可以编写代码说:如果用户点击这个链接,就进入颜色选择页面,然后回到这里。这只是我们利用这种可能性的一个例子。它使我们的软件明显比竞争对手的软件更先进。

相似文章

Vim中的Lisp(2019)

Hacker News Top

详细比较了Slimv和Vlime这两个用于交互式Lisp编程的Vim插件,涵盖安装、功能及推荐。

JavaScript 精简

Lobsters Hottest

LispE 是 NAVER 开发的一个紧凑的 Lisp 方言,它结合了函数式和数组语言特性,并支持 PyTorch 和 llama.cpp 等 AI 库。

热接线Lisp机器

Lobsters Hottest

一位开发者分享了他们使用Emacs和Org-mode构建零依赖静态网站生成器的经验,讨论了现有工具(如org-publish)的局限性,以及他们创建发布方案以保留工作流程的过程。

将 Python 转译为 Lisp

Lobsters Hottest

LispE 是 NAVER 推出的一款开源 Lisp 方言,兼具函数式与数组编程特性,并支持 PyTorch、llama.cpp 以及 MLX 等 AI 库。该语言既可作为原生应用运行,也可打包为支持多线程与现代函数式编程特性的 WebAssembly 库。