纯Scheme语言Web编程工具亮相
速览
该工具完全采用Scheme语言构建,旨在为Web开发者提供更简洁的编程体验。它通过纯函数式范式降低复杂度,可能吸引Lisp爱好者。目前尚不清楚其实际性能与生态支持,但为Web开发提供了新的技术路径。
AI 深度解读
背景
在 Web 开发领域,将高级语言直接编译到 WebAssembly(Wasm)并实现在浏览器中实时运行一直是一个前沿方向。传统上,前端代码的编译依赖服务器端或预构建流程,而 Hacker News 上发布的这篇关于 Goeteia 的文章展示了一种突破性方案:一个纯 Scheme 编写的 Web 编程工具,整个编译器以 Wasm 形式部署在浏览器中,用户编写的 Scheme 代码无需服务器即可在客户端完成编译和执行。该项目自托管的特性(编译器用自身编译的子集编写)和对 Wasm GC(垃圾回收)、尾调用等最新提案的利用,使其在性能、体积和可移植性上具有独特优势。
核心内容
Goeteia 是一个纯 Scheme Web 编程工具。页面中除了这段介绍文字外,所有内容均由 Goeteia 自身渲染。用户编辑的 Scheme 代码会被即时编译为 WebAssembly,并在浏览器中直接运行——整个过程完全在客户端完成,无需服务器参与。页面携带了整个编译器(goeteia.wasm,gzip 后约 70 KB,首次加载后缓存),每次点击“Run”按钮,编译器会在约 15 毫秒内重新编译上方的源代码。
该编译器使用它所编译的 Scheme 子集编写,且实现了自托管:自托管构建过程会重新编译自身,输出结果与原始编译器字节完全相同。每次变更都会通过 CI 方式检查这个不动点(fixpoint),所有测试都会在两个编译阶段(自身编译和自编译输出)中运行。
技术细节方面:
- Fixnum 实现为无装箱的 i31ref(Wasm GC 中的 31 位整数引用),pair(对)和 record(记录)使用 GC 结构体,
eq?是单个ref.eq操作。没有在 JavaScript 中设置影子堆(shadow heap),宿主(JavaScript 环境)仅提供两个字节流导入(byte-stream imports),其他什么都不需要。 - syntax-rules 和过程式 syntax-case(带有 fenders、嵌套省略号以及
datum->syntax)在编译期解释器中运行,通过重命名实现卫生宏。 - 类型化函数引用(typed function references)每个闭包有快速的按参数个数入口和通用入口——变参过程(variadic procedures)和
apply代价低廉,所有尾调用都是return_call(Wasm 尾调用指令)。一个 1 亿次迭代的循环在恒定栈上运行,耗时约 150 毫秒。 - Fixnum 溢出时自动升级为大整数(bignum),使用内联检查(两次位比较);flonum 具有传染性(contagion,即算术运算中自动转换为浮点数);rational(有理数)和 complex(复数)是精确的;浮点字面量由纯 Scheme 编码为 IEEE-754 格式,因此两个宿主(浏览器和 Node.js)发出相同的字节。
- 逃逸续延(escape continuations)利用 Wasm 异常处理提案:捕获是 O(1) 操作,正常路径只消耗一个 try 块,winders(动态风检查)在退出时内层到外层展开。
- 提供了
(web sx)模板(基于细粒度信号(web reactive))、(web html)渲染器以及直接访问宿主的(web js)FFI(外部函数接口)——这个页面就是用这些库构建的。 - 支持 R6RS 风格的
(library ...)文件,带有(import (math utils))解析,依赖优先;导出是建议性的,因为未使用的代码会被自动剪枝。
使用示例:
$ git clone https://github.com/guenchi/Goeteia
$ cd Goeteia
$ ./run-tests.sh # 运行所有测试,包含两个编译器阶段
$ ./build-self.sh # 用自身重新构建编译器
$ echo '(define (fact n) (if (zero? n) 1 (* n (fact (- n 1)))))
(fact 20)' > fact.ss
$ node rt/compile.mjs goeteia.wasm fact.ss fact.wasm
$ node rt/run.mjs fact.wasm
2432902008176640000
编译后的模块可以在任何支持 Wasm GC 和尾调用的引擎上运行:Node 22+、当前版本的 Chrome / Firefox / Safari、wasmtime。从源码引导(bootstrap)需要 Chez Scheme;但检入仓库的编译器 wasm 文件可以直接使用,无需 Chez Scheme。
关键要点
- 纯客户端编译:整个 Scheme 到 Wasm 的编译器(约 70 KB gzipped)随页面加载,无需服务器或预编译步骤,每次编译仅需 ~15 ms。
- 自托管验证:编译器用自身编译的子集编写,通过 CI 保证自编译输出的字节完全一致(fixpoint),所有测试在两个编译阶段运行。
- 无 JavaScript 阴影堆:宿主仅提供两个字节流导入,Scheme 运行时完全依赖 Wasm GC 管理内存(i31ref 优化 fixnum,GC struct 管理 pair 和 record)。
- 高性能尾调用:使用 Wasm
return_call指令,所有尾调用在恒定栈上执行,1 亿次循环仅 150 ms。 - 数值类型自动升级:Fixnum 溢出时检查并提升为 bignum,flonum 具有传染性,精确有理数和复数支持,浮点字面量编码完全由 Scheme 实现(保证跨平台一致性)。
- 逃逸续延零成本捕获:利用 Wasm 异常处理提案,捕获为 O(1),正常路径只增加一个 try 块。
- 完整 Web 开发栈:提供信号驱动模板(
(web sx))、HTML 渲染器((web html))和直接调用 JavaScript 的 FFI((web js)),页面自身即用该栈构建。 - R6RS 库系统:支持
library文件、依赖优先解析,未用代码自动剪枝(export 仅为建议)。 - 广泛的运行时兼容性:编译后的 Wasm 模块可在 Node 22+、Chrome/Firefox/Safari 最新版以及 wasmtime 中运行,无需 Chez Scheme(引导时除外)。
意义与影响
Goeteia 展示了 Scheme 语言在现代 Web 平台上的全新可能性。它将一个完整、自托管的编译器以极小的体积嵌入浏览器,使得用户可以在浏览器中实时编写、编译和运行 Scheme 代码,且性能接近原生。这一模式颠覆了传统上需要服务器或大型开发工具链的 Web 编程交互方式,尤其适合教育、原型设计和线上编程环境。
从技术角度看,Goeteia 充分利用了 Wasm GC、尾调用和异常处理等最新 Wasm 提案,证明了高级语言可以直接、高效地映射到 Wasm 而无需中间层(如 JavaScript 影子堆)。其数值类型自动升级和精确有理数/复数支持,为科学计算和数值应用提供了可靠的方案。自托管的严格字节一致性检查(fixpoint)树立了编译器可靠性的高标杆。
Goeteia 的开放源码(MIT 协议)和基于 R6RS 的库系统,为 Scheme 社区以及 Web 开发者提供了一个可扩展、可移植的基础设施。未来,这种将语言运行时完全编译为 Wasm、依赖宿主仅提供极小接口的模式,可能成为 Web 上运行沙盒化、高性能高级语言的新范式。
