let-go: 為 Go 設計的高性能 Clojure 方言
對於喜愛 Clojure 的表達能力但需要 Go 的部署簡便性的開發者來說,let-go 提供了一個極具吸引力的折衷方案。它是一個用 Go 編寫的 bytecode 編譯器和 VM,旨在實現 Clojure 方言,並專門為速度、小體積和易於分發而設計。
無論您是想構建輕量級的 CLI 工具、基於 WASM 的 Web 應用程式,還是為現有的 Go 應用程式提供腳本層,let-go 提供的運行時啟動時間僅約 7ms——這使其成為 JVM 或甚至是 GraalVM-native 二進位檔的一個極具吸引力的替代方案。
核心架構與性能
let-go 的核心是一個 bytecode VM。透過將類 Clojure 的源碼編譯成預編譯的 bytecode 格式 (.lgb),運行時在載入時跳過了 reader、parser 和 compiler 階段,從而實現近乎瞬時的啟動。
與其他運行時的基準測試比較
與其他 Clojure 實現相比,性能增益最為明顯。在 Apple M1 Pro 上,let-go 在體積和啟動速度方面展現出顯著優勢:
| Metric | let-go | Babashka | Joker | Clojure JVM | | :--- | :--- | :--- | :--- | :--- | | | Binary Size | 10MB | 68MB | 26MB | 304MB (JDK) | | Startup Time | 7ms | 20ms | 12ms | 331ms | | Idle Memory | 14MB | 27MB | 21MB | 92MB |
除了啟動之外,let-go 在計算密集型任務上也極具競爭力。例如,在 Fibonacci 計算中,其性能表現與 Babashka 僅差 4%,並且比使用 tree-walk interpreter 而非 bytecode VM 的 Joker 快約 10 倍。
關鍵特性與能力
獨立執行檔與 WASM
let-go 的首要目標之一是消除對運行時的需求。使用者可以使用 lg -b 將程式編譯成單個、獨立的二進位檔,這會將 bytecode 封裝到 lg 執行檔中。
此外,let-go 支持編譯至 WASM。透過 lg -w,程式可以被編譯成一個自包含的 HTML 頁面。這包括透過 xterm.js 提供的完整終端模擬,允許類 Clojure 程式直接在瀏覽器中運行,並提供原生感的終端體驗。
Go 互操作性
let-go 旨在被嵌入。它允許 Go 開發者將該語言作為腳本層使用,透過將 Go 的值和函數暴露給 VM。關鍵的互操作特性包括:
- Struct Mapping: Go structs 可以被註冊並映射到 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 (Software Transactional Memory),儘管 atoms 和 channels 已得到完全支持。
- Numeric Behavior:
int64上的數值溢出會靜默地進行回繞 (wrap),而不是自動提升為 BigInt。使用者必須使用顯式的 BigInt 數學函數(例如,+'而不是+)。 - Async Implementation: 與 JVM 版本不同,
let-go中的go塊是實際的 goroutines,而不是 IOC state machine。這使得它們更便宜,但也允許它們直接調用阻塞操作。 - 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 server,將 Clojure 強大的交互式開發體驗帶於 Go 生態系統中。