使用Rhombus进行灵活的元编程
摘要
Rhombus是一种新的编程语言,它将Racket强大的元编程能力与类似Python的语法相结合,已发布1.0版本,并由Racket编程语言基金会支持。
暂无内容
查看缓存全文
缓存时间: 2026/07/03 20:16
# 使用 Rhombus 实现灵活元编程
来源:https://lwn.net/SubscriberLink/1079001/67840550991151ed/
## [LWN 订阅者专属内容]
> ### 欢迎来到 LWN.net 以下订阅者专属内容由一位 LWN 订阅者提供给您。成千上万的订阅者依赖 LWN 获取来自 Linux 和自由软件社区的最佳新闻。如果您喜欢这篇文章,请考虑订阅 LWN (https://lwn.net/subscribe/)。感谢您访问 LWN.net!
类 Lisp 语言历来在元编程和灵活性方面处于世界领先地位。虽然许多现代语言已经采纳了宏的概念,但像 Racket (https://racket-lang.org/) 这样的类 Lisp 语言仍在不断突破,力求让宏尽可能容易地融入日常程序。另一方面,Lisp 基于括号的最小化语法可能难以适应——以至于有时人们说 Lisp 代表“令人恼火的愚蠢括号一大堆”。Rhombus (https://rhombus-lang.org/) 是一种新的编程语言,旨在兼得两全其美:将 Racket 的元编程能力与类似 Python 的简洁语法以及合理的标准库默认设置相结合。
#### 项目
该语言是更广泛的 Racket 项目的一部分,该项目“扎根于学术界”。在 Rhombus 的 43 位贡献者中,最活跃的两位分别是犹他大学的 Matthew Flatt 和香港中文大学的 Wing Hei Chan。Rhombus 在两所大学被用作教学工具;Chan 独立贡献于 Rhombus,并不在香港中文大学的计算机科学系工作。尽管如此,该语言的目标不仅仅是学术练习。其开发得到了 Racket 编程语言基金会 (https://racket-lang.org/rplf.html) 的支持,该基金会致力于使 Racket 和 Rhombus 适合现实世界的专业使用。尽管该语言在 2026 年 6 月 22 日才庆祝其 1.0 版本发布,但已经有一套用于 Economancy 卡牌游戏 (https://github.com/oflatt/economancy#economancy) 的工具部分用该语言编写,以及其他非学术用途。
Rhombus 的核心采用 MIT 或 Apache 2.0 许可,但它构建在 Racket 运行时之上,其中一些动态库包含 LGPL 3.0 代码。对 Racket 基础设施的大量重用意味着它已经拥有大量可用库 (https://pkgs.racket-lang.org/) 和一个优化编译器。Rhombus 程序可以被解释执行、编译为字节码或编译为本地机器码。
Rhombus 与 Racket 的不同之处(除了语法之外)还在于其对默认数据结构的选择。类 Lisp 语言通常使用单链表作为核心数据类型,这会带来效率问题。Rhombus 使用由不可变树结构支持的列表,使得许多操作的时间复杂度为对数级别。标准库中还提供了扁平的可变数组、基于树的映射和集合以及基于哈希的映射和集合。
#### 语法
Rhombus 的语法与 Python 非常相似,它基于缩进,并使用 “:” 来指示缩进块的开始。一个细微的区别是,Rhombus 使用 “|” 符号来分隔 if 表达式及类似上下文中的分支。最清晰的解释方式可能通过一个例子(阶乘函数的实现):
```
fun factorial(n):
if n <= 1
| 1
| n * factorial(n - 1)
```
偏向命令式风格的程序员可能更喜欢下面使用 for 循环的版本。注意使用了包含范围 `1..=n` 而不是更常见的排他范围 `1..n`。
```
fun factorial(n):
def mutable p = 1
for (i in 1..=n):
p := p*i
p
```
默认情况下,变量是不可变的,因此需要 `mutable` 绑定操作符 (https://docs.racket-lang.org/rhombus-reference/Mutable_Variables_and_Assignment.html?fam=Rhombus&famroot=rhombus#%28def._%28%28submod._%28lib._rhombus%2Fprivate%2Famalgam..rkt%29._core%29._mutable._rhombus%2Fbind%29%29) 来让循环改变 `p` 的值。然而,这个例子可以更简短。Rhombus 的 for 循环支持一个在类 Lisp 语言之外不常见特性:一个可选的“归约器” (https://docs.racket-lang.org/rhombus-guide/for.html?fam=Rhombus&famroot=rhombus#%28tech._reducer%29),用于将每次循环的结果组合成循环本身的最终值。`math.product` 归约器 (https://docs.racket-lang.org/rhombus-reference/Numbers.html?fam=Rhombus&famroot=rhombus#%28def._%28%28submod._%28lib._rhombus%2Fprivate%2Famalgam..rkt%29._core%29._math._%28math..product._rhombus%2Freducer%29%29%29) 将循环返回的所有值相乘,从而允许这样写:
```
fun factorial(n):
for math.product (i in 1..=n):
i
```
这种能力意味着,Rhombus 中与其他语言中的列表推导等价的东西,仅仅是使用 `List` 归约器 (https://docs.racket-lang.org/rhombus-reference/Lists.html?fam=Rhombus&famroot=rhombus#%28def._%28%28submod._%28lib._rhombus%2Fprivate%2Famalgam..rkt%29._core%29._.List._rhombus%2Freducer%29%29) 来将结果收集到列表中的 for 循环。For 循环还可以使用 `all` (https://docs.racket-lang.org/rhombus-reference/Booleans.html?fam=Rhombus&famroot=rhombus#%28def._%28%28submod._%28lib._rhombus%2Fprivate%2Famalgam..rkt%29._core%29._all._rhombus%2Freducer%29%29) 或 `any` (https://docs.racket-lang.org/rhombus-reference/Booleans.html?fam=Rhombus&famroot=rhombus#%28def._%28%28submod._%28lib._rhombus%2Fprivate%2Famalgam..rkt%29._core%29._any._rhombus%2Freducer%29%29) 归约器来生成映射、集合甚至布尔值。在典型的编程语言中,人们可能期望这种内置语法只适用于标准库提供的类型,而不适用于用户定义的类型。然而,Rhombus 的宏可用于扩展语言的几乎所有部分,包括创建新的归约器。大多数程序可能不需要这样做,但当它能简化程序时,有这个选项还是不错的。
#### 宏
Rhombus 中的宏使用单引号来生成表示 Rhombus 代码片段的语法对象,并使用 “$” 来表示占位符。与使用列表来表示代码的传统类 Lisp 语言不同,Rhombus 的语法对象是它们自己的数据类型。这使得它们可以携带额外的元数据,如行号和位置,这反过来又使 Rhombus 能够产生有用的错误消息,例如正确考虑宏的堆栈跟踪。语法对象还携带控制名称解析方式的元数据,使程序员能够控制宏何时以及如何从环境中捕获名称。既可以编写卫生宏也可以编写非卫生宏 (https://en.wikipedia.org/wiki/Hygienic_macro),但卫生宏是默认选项。
作为一个小型宏的例子,以下是将 Ruby 的后缀 `if` (https://docs.ruby-lang.org/en/master/syntax/control_expressions_rdoc.html#modifier-if-and-unless) 用内置的 `when` (https://docs.racket-lang.org/rhombus-reference/Conditionals.html?fam=Rhombus&famroot=rhombus#%28def._%28%28submod._%28lib._rhombus%2Fprivate%2Famalgam..rkt%29._core%29._when%29%29) 宏实现所需的全部代码:
```
import:
rhombus/meta open // 需要定义宏
expr.macro '$action if $test':
'when $test
| $action'
println("Hi!") if (foo == bar)
```
在某些方面,这种语法对象让人想起 Template Haskell (https://downloads.haskell.org/ghc/latest/docs/users_guide/exts/template_haskell.html) 使用的方法。然而,与 Template Haskell 不同的是,Rhombus 宏可以定义在任何普通函数可以定义的地方,并且响应它们被使用的语法上下文。上面的例子是一个可以在任何期望表达式的地方使用的宏。归约器宏使用 `reducer.macro` 定义,并且还有类似的结构用于自定义导入、模式匹配等处理的宏。
人们可能认为宏可以操作的语法上下文列表是固定的。这在 Rhombus 中并非如此,它向用户公开了操作语法上下文的函数 (https://docs.racket-lang.org/rhombus-meta/ref-space.html#(part._ref-space))。只要程序使用 Rhombus 基本的基于缩进的语法,程序员就可以自由定义他们自己的语法和表达式类型,如果他们觉得有必要的话。
#### 其他形式的扩展
这种灵活性的另一个例子是 Rhombus 对类型检查的处理方式。从某种意义上说,Rhombus 不是静态类型语言。然而,如果添加类型注解,它可以看起来像是静态类型语言:
```
fun factorial(n :: Int) :: Int:
def mutable p :: Int = 1
for (i in 1..=n):
p := p*i
p
// 调用 factorial("foo") 会导致错误消息:
// factorial: 参数不满足注解
// 参数: "foo"
// 注解: Int
```
Rhombus 允许程序员将任意静态信息附加到表达式上,并定义如何推断、扩展、检查和使用这些静态信息来生成机器码的规则。默认情况下,该语言将注解视为渐进类型系统的一部分,尽可能推断类型,然后利用这些来提供错误消息并生成更好的机器码。但是,没有任何东西可以阻止程序员将自己的静态信息类附加到 Rhombus 程序中。例如,可以定义一个用于跟踪程序中敏感数据流、或跟踪函数渐近运行时的注解系统。宏可以访问这些静态信息,以便根据推断的值以不同方式编译表达式。
Rhombus 倾向于在语言自身内部暴露语言的所有实现细节,这是该项目致力于面向语言编程 (https://en.wikipedia.org/wiki/Language-oriented_programming) 的一部分。对于许多任务,用户不需要使用宏;语言提供的普通工具足以满足日常使用。对于那些由于某种原因 Rhombus 已经不适合的任务,该语言鼓励创建微型嵌入式领域特定语言,以尽可能简单地表达问题的核心逻辑。
在许多语言中,一定数量的样板代码是必要的,尤其是对于那些最好用标准库不直接支持的数据结构或关系来描述的程序。Rhombus 允许程序员将这些样板代码一次写入宏中,然后专注于以对他们最有意义的方式表达程序,即使这需要深入审视循环的语义或类型系统的行为。许多这样的微型语言扩展可以在同一个程序中共存,而附加在语法对象上的额外元数据支持创建通用代码格式化器和高亮器、处理自定义绑定宏的跳转到定义支持,以及其他现代语言所期望的便利功能。
为了支持其可扩展性,Rhombus 拥有涵盖语言所有部分的广泛文档 (https://docs.racket-lang.org/rhombus/index.html?fam=Rhombus&famroot=rhombus),从解析一直到编译。Rhombus 继承了 Racket 社区偏爱详尽文档的习惯,这在需要调查语言某个部分的细节时是一种福音,但在只需要一个简单的概述时可能有点冗长。
#### Rhombus 的定位
总体而言,Rhombus 的最佳特性无疑是其可扩展性和文档。在其他方面,如编译速度、执行速度、包生态系统、错误消息等,它处于中间水平:足够好以至于不会妨碍,但也不至于好到超越那些专注于这些领域的现有语言。例如,即使带有完整的类型注解,Rhombus 代码也不太可能在速度上挑战 C 或 Rust——它的性能通常介于 JavaScript 和 Ruby 之间 (https://rhombus-lang.org/performance.html),尽管具体细节因程序和基准测试设置而异。
Rhombus 最有可能发挥作用的领域是那些拥有自定义自制语言,或者使用现有方法表达起来笨拙的问题领域。程序员一直都有能力编写新的专用编程语言,但编写传统优化编译器所需的工作量是巨大的。编写一组 Rhombus 宏则容易得多,并且免费提供了一个现有的编译器、现有库、可用的语法高亮、错误消息和编辑器集成,同时保留了在必要时在最深层定制语言操作的能力。
相似文章
Rhombus v1.0: 一种带有常规语法的Racket风格语言
Rhombus v1.0,一种基于Racket构建的、具有常规语法和强大宏功能的新型可扩展编程语言,已发布。
Racket 的 Shrubbery 语法
Shrubbery 表示法是一种对行和缩进敏感的文本级表示法,用于 Racket 中的 Rhombus 编程语言,旨在部分地对输入进行分组以供进一步解析。
Hyperpolyglot Lisp:Common Lisp、Racket、Clojure、Emacs Lisp
一份对照参考表,比较Common Lisp、Racket、Clojure和Emacs Lisp的语法与特性。
Jam 编程语言
Raphael Amorim 宣布了 Jam,一种旨在结合 Rust 的安全性和 Zig 的简洁性的新编程语言,解决了在 AI 生成代码时代现有系统语言的复杂性和验证开销。
Racket v9.2
Racket v9.2 发布,改进包括更安全的模式匹配检查、Typed Racket 修复、Unicode 17.0 支持,以及许多其他修复和文档更新。