let-go: 为 Go 设计的高性能 Clojure 方言
对于热爱 Clojure 的表达能力但又需要 Go 的部署便捷性的开发者来说,let-go 提供了一个极具吸引力的折中方案。它是一个字节码编译器和 VM,在 Go 中实现了一种 Clojure 方言,专为速度、小体积和易于分发而设计。
无论您是想构建轻量级的 CLI 工具、基于 WASM 的 Web 应用,还是为现有的 Go 应用提供脚本层,let-go 提供的运行时启动时间仅约为 7ms——这使其成为 JVM 甚至 GraalVM-native 二进制文件的极佳替代方案。
核心架构与性能
let-go 的核心是一个字节码 VM。通过将类 Clojure 的源代码编译为预编译的字节码格式 (.lgb),运行时在加载时跳过了读取器 (reader)、解析器 (parser) 和编译器阶段,从而实现了近乎瞬时的启动。
基准测试 vs. 其他运行时
与其他 Clojure 实现相比,性能提升最为明显。在 Apple M1 Pro 上,let-go 在体积和启动速度方面表现出显著优势:
| 指标 | let-go | Babashka | Joker | Clojure JVM |
|---|---|---|---|---|
| 二进制文件大小 | 10MB | 68MB | 26MB | 304MB (JDK) |
| 启动时间 | 7ms | 20ms | 12ms | |
| 空闲内存 | 14MB | 27MB | 21MB | 92MB |
除了启动速度外,let-go 在计算密集型任务上也极具竞争力。例如,在斐波那契数列计算中,其性能接近 Babashka 的 96%,并且比使用树遍历解释器而非字节码 VM 的 Joker 快约 10 倍。
关键特性与能力
独立可执行文件与 WASM
let-go 的主要目标之一是消除对运行时的需求。用户可以使用 lg -b 将程序编译为单个、独立的二进制文件,该命令会将字节码打包进 lg 可执行文件中。
此外,let-go 支持编译为 WASM。通过 lg -w,程序会被编译成一个自包含的 HTML 页面。这包括通过 xterm.js 实现的全功能终端模拟,允许类 Clojure 程序直接在浏览器中运行,并提供原生般的终端体验。
Go 互操作性
let-go 旨在被嵌入使用。它允许 Go 开发者通过向 VM 暴露 Go 值和函数,将该语言作为脚本层来使用。关键的互操作特性包括:
- 结构体映射 (Struct Mapping): Go 结构体可以被注册并映射到 let-go records,从而实现未变更数据的零成本往返。
- 通道集成 (Channel Integration): Go channels (
chan) 直接接入let-go的core.async实现,从而在两种语言之间实现无缝的事件管道传输。 - 函数调用 (Function Calls): Go 函数可以直接从 let-go 代码中调用,反之亦然。
生态系统兼容性
为了保持与 Clojure 的高度兼容,let-go 通过了 clojure-test-suite 的测试,通过了超过 95% 的断言。它实现了核心命名空间,如 clojure.core、clojure.string、clojure.set 和 core.async。
有趣的是,let-go 还支持 Babashka pods。这使得它能够访问庞大的独立程序生态系统,这些程序通过二进制协议暴露命名空间,从而无需重写库生态系统即可立即访问数据库、AWS 和 Docker 集成。
与标准 Clojure 的差异
虽然 let-go 旨在提供高保真度的体验,但它并不是 JVM Clojure 的直接替代品。开发者应当注意以下几个关键差异:
- 并发 (Concurrency): 它缺少 Refs 和 STM (软件事务内存),尽管 atoms 和 channels 是完全支持的。
- 数值行为 (Numeric Behavior):
int64上的数值溢出会静默地进行回绕 (wrap),而不是自动提升为 BigInt。用户必须使用显式的 BigInt 数学函数(例如,使用+'而不是+)。 - async 实现 (Async Implementation): 与 JVM 版本不同,
let-go中的go块是真正的 goroutines,而不是 IOC 状态机。这使得它们更廉价,但也允许它们直接调用阻塞操作。 - 正则表达式 (Regex): 它使用 Go 的
re2风格的正则表达式,而不是 Java 的。
入门指南
对于有兴趣尝试的用户,可以通过 Homebrew 或 Go 安装 let-go:
brew tap nooga/let-go
brew install let-go
或者通过 Go install 安装:
go install github.com/nooga/let-go@latest
安装完成后,您可以使用 lg 启动 REPL,或者使用 lg myfile.lg 运行文件。对于高级工作流,lg -n 命令可以启动一个与 CIDER、Calva 和 Conjure 兼容的 nREPL 服务器,将 Clojure 强大的交互式开发体验带入 Go 生态系统。