为什么许多编程语言对换行甚至缩进情有独钟?

Rhombus有感,当年扬言revenge of the son of the lisp machine的PLT众居然,不但叛逃到换行敏感语言的阵营,还引进了缩进敏感这种玩意,甚至搞出来用非ASCII字符分组这种操作。这还不是第一次,在Pyret上尝试过了,尽管关于缩进敏感性他们“haven’t yet decided on”。

然后转念一想,至少在我所知道的范围内,从sh(这个尚且可以理解)到Python、Ruby、Lua、JavaScript、各种别的Script,几乎所有主流的“脚本”或者“轻量级”语言都至少以可选的形式将换行甚至缩进作为与空格不同的分隔符。“严肃”或者“系统”的编程语言也不能免俗,Go、Kotlin纷纷抛弃其老祖宗的分号,Haskell和Scala连缩进都引入了。近几年来比较火热的编程语言,似乎只有Rust死硬地将所有whitespace一视同仁?

但问题是,所谓的“free-form”真的有那么不堪吗?即便是据说代码的最后一页全是右括号的Lisp,想要流畅阅读其实只需一个rainbow delimiter,想要流畅编辑只需一个auto-pair(单说最平凡的基础支持;上Paredit之类的就太欺负人了,更别说真·结构化编辑器)。C和Rust在同等抽象级别的语言中也没有任何特别的可读性缺陷(当然有IOCCC这种玩意,然而其格式化不了的晦涩部分也跟lexical structure没多大关系)。反观换行敏感语言,一旦遭遇大量嵌套,尤其是boilerplate性质的,就可能变得非常冗长。如果说这个可以通过可选的分号来解决,那么代码复制粘贴的问题就必须求助编辑器支持了。而以被戏称为“需要借助游标卡尺编程”的Python为代表的缩进敏感语言就更是重量级了,不说CV工程的问题,编辑长代码的时候没有一个syntax hierarchy的提示根本不知道自己从哪来到哪去。与之相比,据说非常“反人类”的大量嵌套括号和令一些人不胜其烦的分号简直是友好到家了。而“便于入门”的优势也是很可疑的,因为并不能控制变量,Python巨大的标准库和浩如烟海的生态已经形成马太效应了,很难断言语法在它的成功中占据了多大的比重。在这样的情形下,大量新兴语言选择换行/缩进敏感的道路,让我非常不能理解。

Go FAQ:

Semicolons, however, are for parsers, not for people, and we wanted to eliminate them as much as possible.

缩进的话, 我确实也不能理解. 这玩意甚至不能用上下文无关文法表示吧

有一说一,其实Scheme/Racket的quasiquote也不能(我没有研究过;R7RS上是这么说的)。

排除万难也要弄语法糖确实是新兴语言内卷的常见方式。比如Julia的作用域规则,为了尊重某种从Python、MATLAB以及其他我不知道的东西那里来的“常识”,简直是拼了。

好吧, 其实对 Python 代码预处理一下也是可以用上下文无关文法的. 不过在复制粘贴 Python 代码的时候是真的难受. 另外在 Python shell 中, 代码块与代码块之间也必须保留一个空行, 我可以理解这是为了标志代码块的结束, 比如

>>> def f():
        pass
此处空行是必须的
>>> 1+1

但是顶格写一个新的代码块同样能表示代码块的结束. 否则源文件中的内容是不能直接粘贴到 python shell 中.

这也是好笑, 明明缩进的宽度是自动推断的, 但是 PEP8 又给出了推荐的宽度, 为什么不干脆一开始就统一呢? 还不如强制用单个 tab 来缩进

JavaScript和Ruby什么地方对Tab和Spaces有区分?

使用 Offside rule 的影响来自于 1966 年发表的 ISWIM,也就是 PL 任耳熟能详的 the next 700 languages

初衷很简单,ISWIM 不是实际实现的语言,比起方便实现用程序 parser,它更主要用来手写和 TeX 排版里交流算法代码,在没有 IDE 辅助情况下,显然用缩进而不是成对结构对人类更友好,而且当时 TeX 排版代码不是用工具生成 TeX,而是直接手打 TeX 命令,省略不必要的结构有效提高写论文效率 当时还没有 TeX,论文是手工铅排的,作者在后记中提到为了保证排版正确把校对版打回重排了三次,不过对最终效果还是十分滿意。在只能单色打印的时代,能用不同字体来“高亮”关键字而不须用等宽字体,也能提高论文可读性。

ISWIM 影响众多后来的函数式风格语言,包括 1971 年的 Axiom CAS,用 Lisp 实现了一个使用 offside rule 的扩展语言编译器,以及 1972 年实现的 ISWIM 的可执行子集 SASL,它的后继者 Miranda 和 Haskell

4 个赞

我说“换行甚至缩进”是泛泛而谈,不是说每一个都既对换行敏感又对缩进敏感

因为 RnRS 的 quasiquote 形式化语法需要额外把

`(,,a b)

这种实践中是在预处理展开中报错的表达式视为语法错误。不代表 Scheme 不是 CFL。

XML/HTML 也是类似道理,不能用 CFG 表示 XML tag 的匹配,但是保证 tag 匹配正确后就能符合 CFG。

读 Lisp 代码不也是看缩进的吗?总不能真的对括号吧。我从来不开彩虹括号,只有写 tsx 的时候觉得用的到。

我看scheme代码确实需要用尺子

另外我觉得F#的设计很有趣,同时提供对缩紧敏感(light)和不那么敏感(verbose)的两套语法,这两种语法可以在同一源文件里无缝切换。https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/verbose-syntax

1 个赞

这是不是有点想当然了.

纸质书谁也救不了,缩进还不够,最好每行前面加上竖线,代码块周围环绕各种制表符,强行模拟编辑器环境[1];要么就退缩到纯指令性的语言,一行一个命令,代码块很少并且用beginend之类的分隔。如果这些都不够……我估计这就是为什么由各种不同字体的单个字母、鬼画符、前中后缀、上中下标组成的数学标记法能够经久不衰。


  1. 为什么主流引擎不把tab(U+0009)渲染成这样?这样编辑器可以偷懒,这个字符的定位不再那么尴尬,缩进敏感的语法设计似乎也合理了起来,对大家都有好处。 ↩︎

Lisp代码严格按照缩进约定很容易变成“一柱擎天”。我写Scheme都是把连在一起的lambdamatch、named let等等,以及简单的控制流全部挤在一行的。