再借楼发(跑)散(题)超纲一点,我想特别显示一下在 lang spec 里钦定 phase 这个做法的实际历史性后果,以说明它是如何比 PL 壬沉溺(钦定设计的) type system 和相关研究更优先值得黑的。
钦定 phase 的一个显著后果单方面由语言设计者决定了何为“静态”,而没有用户议价的空间,导致不切实际的无效工作。
一旦不满足用户需要,要维持可编程性,就需要 ad-hoc (致敬 ad-hoc polymorphism ,表示“不一定就是坏的”,但实际大家都懂的)的 reflection 机制来变通。
这种机制基本也只可能由 spec 钦定,于是设计语言时就凭空多出来本不必要的一大坨的 API 设计(经常还没法避免主观性,特别是甄选什么 feature subset 配被 reflect )的工作:工作量大体 O(n^t),其中 t 是 phase 数,常量和其它特性正相关。(虽然大 O 是渐进上界,但常数可不小,一般要可用也够受的了。)
大部分语言中 t=2 ,被隐含钦定的 phase 是 phase of translation 和 phase of execution 两个,这也是一般所谓语言是否可被视为“静态”的分野。光是这个就制造出了反射这种原本就是同一个直接语义直接元编程的就能“免费”实现(源语言语法满足 homoiconicity 时)的工作。而 C/C++这种直接钦定一大坨 phase 的做法更加突出了所谓 compilation-time reflection 的更欠抽的“高阶”无谓设计和实现工作量。(当然,C 一直装死摆烂,这里可以算了。)(还好,除了 C++主流语言里还有谁那么乱的么……)
第二个后果是简单粗暴的 phase 蕴含了粗放式分层架构,即便实际的需求中,各个 phase 的问题域具有局域性,而并不要求语言规则具有明确的分层结构。
这是 reflection 这样的机制难以被其它形式的元编程替代的另一个原因。
这里有个类型系统外过于“静态”的例子:hygienic macro 再强,就算 host lang 再 homoiconic ,基本也得多一个 template sublanguage (而增加无谓设计和用户学习的工作量)——很大程度是多了个 expansion-time phase 的锅。
作为对比,bind-time analysis 中多出来的就是 local stage ,不添加类似的缺陷。
第三个后果是对实现结构的影响(不过其实 C 这种传统设计其实算是直接泄漏了实现的设计),尤其是造成片面拔高 AOT 编译的能力的偏见,工程上力大砖飞,搞不好就加人,全然不管项目规模扩大的代价,特别是路径依赖。
(幸甚,multi-staged ML/reflective Lisp 之流从来不成气候,所以毒害的主要也就局限在传统“静态”语言实现了……)
当年编译器理论界对 nanopass 的普遍怀疑也说明这不仅仅是工业界见识短的问题,“理论界”也整体就跑偏了。
近年来 PyPy 和 Graal+Truffle 这样更正统(指 variants of Futamura projection )的计科传统艺能(指无情地自动化加抽象)重新上线,让一些实现者认识到现有方法论的欠缺(你人再多能打得过自动化翻译解释器成量产优化编译器么),算是自底向上地打了脸。
遗憾的是,这个问题在工业界距离彻底纠正差得远了:可预见的未来内要指望主流工业界开窍避免依赖 GCC 和 LLVM 这样的力大砖飞式历史包袱是十分不现实的。
其它理论碎碎念:
github.com/FrankHB/pl-docs/blob/master/en-US/calling-for-language-features.md#phases-and-stages (关于这些问题如何破坏语言的通用性,Ctrl+F smoothness 。)