LWN:Elisp里另一种模式匹配条件执行方式!

共 6615字,需浏览 14分钟

 ·

2024-04-12 00:13

关注了就能看到更多这么棒的文章哦~

An alternate pattern-matching conditional for Elisp

By Jake Edge
March 1, 2024
Gemini translation
https://lwn.net/Articles/961682/

我们在十一月份看到过 的关于在 Emacs Lisp(Elisp)中使用 Common Lisp 特性的(非常)长的讨论,带来的结果之一就是开始了把 Common Lisp 从 Emacs 中移除掉。Richard Stallman 开始了对使用 Common Lisp 库(cl-lib)的 Emacs 中 Elisp 的一些重写,以减少维护 Emacs 本身所需的认知负荷。自那时起,他努力简化 Elisp 的工作,通过添加一个新的模式匹配条件,希望替换长期以来他认为过于复杂的pcase。

复杂之处

在十一月中旬,Stallman指出,他发现 pcase 定义的他称为“little language”的逻辑“简洁到让人难以理解”。他认为解决相同的一组问题,采用的把更简单的 Elisp 构造,比如cond和let 结合起来的做法,是“冗长且麻烦的”,但 pcase 已经将对简洁性的渴望推到了一个不可取的极端。他说,这给所有不得不维护使用 pcase 的 Emacs 开发人员带来了成本,因此他决定将一些 pcase 特性调整到其他构造(constructs)中。

可预见地,这引发了一系列长篇讨论(emacs-devel 邮件列表的常见情况)讨论是否需要 pcase 的替代方案,替代方案可能是什么样子,等等。Stallman新开了一个子线程来研究他对一个新宏 cond* 的想法,它将提供一个更简单的模式匹配构造,仍然比使用“老式的 Lisp”更简洁。他的新宏旨在结合条件 cond 形式,它处理检查多个不同值–类似于其他语言的(switch构造)与 let ,它在受限范围内临时把值绑定到变量。 pcase 和 cond* 都旨在为 Elisp 提供一个ML样式的模式匹配条件机制。

一个简单的 pcase 示例可以展示出这些构造的一般风格,但两者能做的功能要更多,包括模式匹配和解开列表(pulling lists)和其他数据结构,这就是“解构(destructuring)”。这个例子,摘自 pcase 文档,它处理了多种不同类型(例如字符串,符号)的返回代码,为每个生成了一个适当的消息:

(pcase (get-return-code x)
;; string
((and (pred stringp) msg)
(message "%s" msg))

;; symbol
('success (message "Done!"))
('would-block (message "Sorry, can't do it now"))
('read-only (message "The schmilblick is read-only"))
('access-denied (message "You do not have the needed rights"))

;; default
(code (message "Unknown return code %S" code)))

Stallman 将他的方法描述为试图“避免 pcase 的应有尽有所导致的笨拙”。自然地,这引发了对 pcase 的进一步争论。例如,Michael Heerdegen说:“在我看来‘pcase’非常接近完成这个任务的最佳解决方案。”

到了十二月中旬,Stallman询问了 cond* 所需的特性“以便很少遇到无法干净地取代 pcase 的情况”。这个对话发生在 pcase 替换讨论的另一个分支中,这使得它有点难以跟进。在这个线程中,其他人正在与 Stallman 一起调整 cond* ;同样,其中有很多有帮助的建议和澄清疑问,还夹杂着一些抱怨。然而,Stallman 显然正在推进这个功能。

在十一月中旬,Alan Mackenzie 引发了一个问题,似乎自 2010年添加 pcase 以来就一直存在的问题:文档。他指向了他在 2015 年发表的一个帖子,描述了 pcase 文档存在的问题;当时已经解决了其中很多问题,但还有一个正在进行的工作,由 Jim Porter 领导来改进这个宏的文档。

Porter 列举了需要关注的多个方面,包括将用于模式匹配和解构的反引号("`")运算符的呈现提前放在 pcase 文档字符串(doc string)中。正如 Mackenzie 所指出的, pcase 令人困惑的一点是它使用了两个标点符号,即反引号和逗号(","),这两个标点符号在 Elisp 宏定义中已经有了确定的用法:“在 pcase 之前,这两者有明确的含义。但是此后它们变得高度依赖上下文。”

对于 Porter 的帖子有一些回应,大多数都赞成他的观点;最终,Emacs 共同维护者 Stefan Kangas将该帖子复制给了前 Emacs 维护者、 pcase 的开发者 Stefan Monnier。Monnier也基本上同意;他认为文档字符串实际上并不是详细介绍反引号信息的地方,尽管进行一些语言上的重新调整是有道理的。Stallman对Porter的其他建议提出了异议,可能在“《[在 Emacs Lisp 中进行编程的介绍》](https://www.gnu.org/software/emacs/manual/html_node/eintr/index.html)”中提到 pcase 。"我们不应该鼓励人们学习 Emacs Lisp 来使用 pcase 。""

初稿

一月中旬,Stallman发布了cond*的初稿,要求人们进行更多的测试,期待“富有建设性的意见、错误报告、补丁和建议”。Andrea Corallo询问了关于 cond* 的一个特定特性:

一些 cond* 子句为什么要让变量绑定在这个子句之外(在整个 cond* 结构范围内)的原因是什么?乍一看对我来说在 Lisp 术语中这种做法并不是很常见。

Corallo 指的是 bind* 子句,它可以出现在 cond* 的主体中的任何地方,以用于创建变量的绑定,其作用域并不在包含它们的 bind* 结束的位置。而是从那一点开始生效,这些变量的作用域就是 cond* 的主体,这与 Lisp 的通常期望有些不一样。

(cond*
(CONDITION FORM)
((bind* (x 42))) ; create a binding of 42 to x
(CONDITION-using-x FORM-using-x)
...)


正如 Távora指出的,其他人已经询问过这种行为,但他“不确定最终是否澄清了”。他还想知道 cond* 是否可以使用 pcase 来构建;如果它是 pcase 功能的一个严格子集,可能有助于这样做。这“实际上可能会为‘cond*’的采用路径(尽管这条路径对于某些人来说仍然是有疑问的)提供便利”。但是 Stallman并不这样看待;他列举了他认为 cond* 的优势,并表示他打算将这个新的宏添加到 Emacs 中:

与 pcase 相比,cond*具有四个基本优势:可以使绑定覆盖整个主体、匹配各种数据对象的模式(不一定是相同的)、在子句中使用普通的 Lisp 表达式作为条件、以及[能够]进行绑定并继续使用进一步的子句。

我将进行更多测试,然后让 cond* 功能上线。

Adam Porter要求Stallman 重新考虑将 cond* 安装到 Emacs 中的做法,建议他应该考虑增强 pcase 而不是添加一整个新的基础设施让开发人员来学习。 pcase 的抱怨之一是学习的负担;“需要学习 pcase 和 cond* 这两者了,这无助于减轻人们的学习成本。”Adam Porter 指出 pcase 可以处理 Stallman 所列举的许多优势,因此可能有一条增强 pcase 的路径:

您编写 cond* 的陈述理由包括了 pcase 的一些局限性。其中一些,例如文档,已经有志愿者站出来解决。其他的问题也可以通过各种方式解决。我已经提出了一些建议,但您没有解释拒绝它们的原因。

不出所料,Stallman不同意:

如果 pcase 对于某些特定任务缺少功能,通过添加一些功能来解决问题将会很容易。然而,pcase 的问题在于它对于它所做的工作来说具有了太多的功能。cond* 可以用更少的功能做同样的工作,因为它们更好地协同工作。

ELPA?

Kangas 表示他并未认真关注讨论,但对“计划安装'cond*' 的做法”感到惊讶,“否则我会更早地发表意见”。添加 cond* 将导致 Emacs 的维护工作变得更加困难,因为 pcase 并没有被淘汰,因此将会有“不止一个相对复杂的宏,而是变成了两个宏”需要他了解和理解。如果 cond* 带来了实质性的好处,那么这样做可能值得,但他并不认同;“我所看到的只是'pcase'的一种版本。”因此,他建议为GNU Emacs Lisp Package Archive(ELPA)创建一个新包,这将提供“探索现有宏的替代版本的良好途径”。

Kangas 认为没有全面替换 pcase 的计划是一个好事。这样避免了自然而然会导致的大量代码变动和错误。但 Mackenzie持相反意见;他认为完全替换 pcase 将是“一举改善我们代码的可读性和可维护性”的良好目标。他认为将其放入 ELPA 库中“意味着它永远不会有所进展,只会被遗忘”;而在 Emacs 主分支中开始功能分支并最终合并将是更好的做法。

与其他几人一样,Monnier认为 cond* 只是又进一步复杂化了 Elisp。另一方面,他说,对他来说反对它有点虚伪:

所以,我并不是非常热衷于添加这样一个新形式,但我作为多年来引入 ELisp 中几种这样的新构造的责任人,我感觉有点像一个褒贬不一的人。

所以,与其反对它,我更愿意指出一些我认为可以改进的事情(或令我不快的事情)。

随后 Stallman 和 Monnier 就 cond* 以及它与 pcase 的重叠(以及两者可能如何“达成妥协”)进行了一连串的讨论,以及关于传统 cond 形式在绑定条件内部存在的一些不足的讨论等。讨论的大部分围绕着 Monnier 的担忧展开,他认为两种构造有重叠但仍有不同的模式语言:

[…]我看到一个相当丰富但硬编码的模式语言,与 Pcase 相比,这似乎是一个倒退,Pcase 的模式语言只是由几个基本原语组成的(其他部分是通过'pcase-defmacro'定义的)。最糟糕的是,两种模式语言非常相似,但我看不出有任何好的理由让它们不同。

Monnier强调Stallman 可以直接重用 pcase 的低级模式语言用于 cond* ,这将带来许多好处:“工作量更小,代码重复更少,文档重复更少,对于程序员学习的内容也更少。而且很可能您会随后完善 Pcase,这样每个人都会受益。”

截至目前仍在进行讨论,尚不清楚 Stallman 将如何处理模式语言的问题。Monnier称,通过相应的补丁可以很容易将 match* 形式更改为使用 pcase 机制;相关的补丁已经被发送。Mackenzie反对 cond* 使用 pcase 的模式处理方式,部分原因是因为缺乏对低级 pcase 机制的文档说明。Alfred M. Szmidt希望最终 pcase 可以被编写成使用 cond* ,而不是相反,但 Monnier表示这在技术上是不可行的。

添加 cond*

一月底,Emacs 的共同维护者 Eli Zaretskii 和 Kangas宣布 cond* 将被安装到 Emacs 核心中,作为 pcase 的替代方案;然而,并没有放弃 pcase ,因为它被认为是一种“风格偏好问题”。在帖子中,Kangas 明确表示政治上而不是严格的技术考虑是决策过程的一部分:

作为维护者,我们的首要责任是确保我们都能够共同努力,团结在同一个旗帜之下。我们项目的成功取决于此。因此,我们最不希望做的事情是排斥任何一组贡献者,无论人数多少。

我们相信这比对 cond* 或 pcase 的论证更重要。简单的事实是我们拥有不同的背景和经验,这些经历往往使我们位于这个讨论的两边。这种多样性是一种力量,而不是弱点。

尽管如此,显然仍然有一些人对 14 年前 pcase 被安装到 Emacs 中感到不满,至少对于 Mackenzie 来说,是这样的。他认为维护者选择的中庸之道不会有助于解决他和其他人在理解一些 pcase 使用方面遇到的问题;他仍然主张使用 cond* 进行全面替换。另一方面,Stallman认为不应该替换=pcase=,但“希望避免在 Emacs 中使用 pcase,应该支持 cond*”。当然,这一决定并没有阻止另一场关于“ cond* 与 pcase ”的讨论持续展开,自然也一直在进行中。

一月底,Stallman表示他几乎准备好将代码提交到Emacs Git存储库的代码,尽管截至目前尚未提交。Zaretskii要求同时将文档添加到Elisp参考手册,因此这可能会拖慢进程。不过过不了多久, cond* 应该会出现在 Emacs 中,因此将会有两种方式来进行模式匹配和解构能力的代码实现——无论这是好事还是坏事。

全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~



浏览 23
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报