基于Go的Clojure
摘要
Glojure是一个开源的、基于Go的Clojure语言解释器,能够无缝访问Go库,并允许嵌入到Go应用程序中。它目前处于早期开发阶段,但已用于业余项目。
查看缓存全文
缓存时间: 2026/06/18 02:45
glojurelang/glojure 源地址:https://github.com/glojurelang/glojure # Glojure 示例工作流 在浏览器中尝试!(https://glojurelang.github.io/glojure/) Gopher 图片衍生自 @egonelbre(https://github.com/egonelbre/gophers),采用知识共享 1.0 署名许可协议(https://creativecommons.org/licenses/by/1.0/)。 Glojure 是一个 Clojure(https://clojure.org/)解释器,托管在 Go 上。Glojure 提供了对 Go 库的轻松访问,类似于 Clojure 提供对 Java 框架的轻松访问。Glojure 目前处于早期开发阶段;可能存在 bug、缺少功能以及性能有限。在 v1 版本发布之前,不保证向后兼容。尽管如此,它已成功用于爱好项目,并运行了(转换后的)核心 Clojure 库的相当一部分子集。
请注意,与大多数其他 Go 实现的 Clojure 不同,Glojure 是一种“托管”语言——这个术语用于描述基于宿主语言(这里指 Go)实现的语言。这意味着所有 Go 值都可以用作 Glojure 值,反之亦然。
先决条件
在开始使用 Glojure 之前,请确保已安装并了解 Go(版本 1.19 或更高)。
安装
目前,Glojure 可通过源代码在所有支持 Go 运行的平台上获得,并且至少需要 Go 1.24。使用 go install 命令安装:
$ go install github.com/glojurelang/glojure/cmd/glj@latest
安装后,可以通过 glj 命令启动 REPL(读取-求值-打印-循环):
$ glj
user=> (println "Hello, world!")
Hello, world!
nil
user=>
用法
Glojure 可以通过两种方式使用:作为独立命令行工具(glj)或嵌入到 Go 应用程序中。
使用 glj 命令
glj 命令提供了传统的 Clojure 开发体验:
显示帮助:
$ glj --help
# 或 glj -h
显示版本:
$ glj --version
glojure v0.3.0
启动 REPL(交互式会话):
user=> *glojure-version*
{:major 0, :minor 3, :incremental 0, :qualifier nil}
$ glj
user=> (+ 1 2 3)
6
user=> (println "Hello from Glojure!")
Hello from Glojure!
nil
REPL 特性
交互式 REPL 包含:
- Vi 和 Emacs 编辑模式——默认为 vi;可通过
~/.inputrc配置 - 多行编辑——不完整的表达式会自动换行并自动缩进
- Tab 补全——符号、命名空间和别名带有描述性标签
- 智能缩进——Tab 插入 2 个空格;Backspace 删除一个完整的缩进级别
- 持久化历史——跨会话保存到
~/.glj_history - 括号粘贴——即时粘贴代码块
- 作业控制——Ctrl+Z 暂停,
fg恢复 - 中断——Ctrl+C 取消输入或中断求值
求值表达式:
$ glj -e '(println "Hello, World!")'
Hello, World!
$ glj -e '(apply + (range 3 10))'
42
$ glj -e '
(defn factorial [n]
(if (<= n 1)
1
(* n (factorial (dec n)))))
(factorial 5)'
120
运行 Clojure 脚本:
;; hello.glj
(println "Hello," (first *command-line-args*))
$ glj hello.glj World
Hello, World
创建可执行程序:
;; server.glj
(ns example.server)
(defn echo-handler [w r]
(io.Copy w (.Body r))
nil)
(net:http.Handle "/" (net:http.HandlerFunc echo-handler))
(println "Server starting on :8080...")
(net:http.ListenAndServe ":8080" nil)
$ glj server.glj
Server starting on :8080...
在 Go 应用中嵌入 Glojure
你也可以将 Glojure 作为脚本语言嵌入到你的 Go 应用中。当你需要以下功能时,这非常有用:
- 为你的 Go 应用添加可脚本化的配置
- 允许用户使用 Clojure 插件扩展你的应用
- 将 Go 的性能与 Clojure 的表现力结合起来
- 控制执行环境(自定义 I/O、沙箱)
基本嵌入示例:
package main
import (
"fmt"
_ "github.com/glojurelang/glojure/pkg/glj" // 初始化 Glojure
"github.com/glojurelang/glojure/pkg/runtime"
)
func main() {
// 求值 Clojure 代码
result := runtime.ReadEval(`
(defn factorial [n]
(if (<= n 1)
1
(* n (factorial (dec n)))))
(factorial 5)
`)
fmt.Printf("5! = %v\n", result) // 5! = 120
}
从 Clojure 调用 Go 以及从 Go 调用 Clojure:
package main
import (
"fmt"
"github.com/glojurelang/glojure/pkg/glj"
"github.com/glojurelang/glojure/pkg/runtime"
)
// 定义一个 Go 函数
func greet(name string) string {
return fmt.Sprintf("Hello, %s from Go!", name)
}
func main() {
// 将 Go 函数暴露给 Clojure
runtime.ReadEval(`(def greet-from-go nil)`) // 占位
greetVar := glj.Var("user", "greet-from-go")
greetVar.SetRoot(greet)
// 从 Clojure 中使用它
result := runtime.ReadEval(`(greet-from-go "Clojure")`)
fmt.Println(result) // "Hello, Clojure from Go!"
// 从 Go 调用 Clojure 函数
runtime.ReadEval(`(defn add [x y] (+ x y))`)
addFn := glj.Var("user", "add")
sum := addFn.Invoke(10, 32)
fmt.Printf("Sum: %v\n", sum) // Sum: 42
}
访问你自己的 Go 包: 嵌入 Glojure 时,你还可以使用下面访问额外的 Go 包一节中描述的包映射方法,暴露你自己的 Go 包或额外的标准库包。这允许嵌入的 Clojure 代码访问你选择暴露的任何 Go 包:
import (
_ "github.com/glojurelang/glojure/pkg/glj"
_ "your.app/gljimports" // 你生成的包映射
)
// 现在 Clojure 代码可以访问你暴露的包
runtime.ReadEval(`
(your$package.YourFunction "arg")
(another$package.Method)
`)
何时使用每种方法
使用 glj 命令:
- 编写独立的 Clojure 程序
- 使用 REPL 进行交互式开发
- 运行 Clojure 脚本
- 直接从命令行求值表达式
- 结合 Go 互操作学习 Clojure
嵌入 Glojure:
- 为现有的 Go 应用添加脚本功能
- 构建一个平台,用户可以使用 Clojure 扩展它
- 自定义控制 Glojure 执行环境
- 在一个二进制文件中混合 Go 和 Clojure
互操作
Glojure 开箱即用地支持与许多标准库包的互操作。为了避免与使用 / 引用命名空间符号产生歧义,Go 包名被做了混淆处理:包名中的 / 被替换为 :。以下是一个简单示例:
user=> (println (fmt.Sprintf "A couple of HTTP methods: %v" [net:http.MethodGet net:http.MethodPost]))
A couple of HTTP methods: ["GET" "POST"]
nil
默认包含以下标准库包:
bytescontexterrorsflagfmtioio/fsio/ioutilmathmath/bigmath/randnet/httposos/execos/signalregexpreflectsortstrconvstringssyncsync/atomictimeunicode
要暴露额外的包,你需要生成一个“包映射”,并编译你自己的可执行文件,其中导入你的包映射和 Glojure API。更多细节请参见以下小节。标准库包的可用性和互操作工作流都将得到改进。
访问额外的 Go 包
gen-import-interop 可用于生成一个 .go 文件的内容,该文件导出一个函数,用于将额外包的导出添加到 Glojure 包映射中。
$ go run github.com/glojurelang/glojure/cmd/gen-import-interop \
-packages=:comma-separated-package-list: \
> your/package/gljimports/my_package_map.go
然后,在你的程序中:
package main
import (
// 将你的包的导出添加到 pkgmap 中。
_ "your.package/gljimports"
)
// ...
与 Clojure 的差异
数值
| Clojure 类型 | Glojure 类型 | 备注 |
|---|---|---|
long | int64 | |
double | float64 | |
float | float32 | |
byte | byte | 注意 Go 的 byte 是无符号的,而 JVM 的 byte 是有符号的。 |
short | int16 | |
int | int | 注意 JVM 的 int 是 32 位,而 Go 的 int 根据平台可能是 32 位或 64 位。 |
char | lang.Char | Glojure 类型是一个带标签的 rune(type Char rune)。JVM 的 char 是 16 位,而 Go 的 rune 是 32 位。 |
BigInt | *lang.BigInt | Glojure 类型包装了 *big.Int。 |
BigDecimal | *lang.BigDecimal | Glojure 类型包装了 *big.Float。 |
Ratio | *lang.Ratio | Glojure 类型包装了 *big.Rat。 |
BigInteger | *big.Int | 原生 JVM BigInteger 对应 *big.Int。 |
与其他 Go 移植版 Clojure 的比较
| 方面 | Glojure | Joker(https://github.com/candid82/joker) | let-go(https://github.com/nooga/let-go) |
|---|---|---|---|
| 托管语言1 | 是 | 否 | 否 |
| 可扩展的 Go 互操作 | 是 | 否 | 否 |
| 并发 | 是 | 是(带 GIL) | 是 |
| Clojure 工具(如 linter) | 否 | 是 | 否 |
| 执行方式 | 树遍历解释器 | 树遍历解释器 | 字节码解释器 |
如果你想看到此表中的另一个移植版,或认为其中有错误,请提交 issue 或 pull request!
什么是托管语言?对于 JVM 上的 Clojure,这意味着所有 Java 值也都是 Clojure 值,反之亦然。Glojure 力求与 Go 保持同样的关系。
相似文章
Clojure 速度几乎媲美 C(需借助一些优化)
本文详细介绍了 Clojure 如何借助 JVM 的 Vector API 和精心优化,在 3D 压力测试中达到接近 C 的帧率(仅差 20%),展示了动态语言在热循环中也能接近底层性能。
Go 语言服务器可以实现令人印象深刻的代码导航
Go 语言服务器 (gopls) 为 Go 开发者提供了令人印象深刻的代码导航功能,增强了 IDE 的能力。
jank 现已拥有自己的自定义 IR
jank 是一种 Clojure 方言,现已引入一种在 Clojure 语义层面设计的自定义中间表示,以实现更好的优化并与 JVM 竞争。
使用Clojure约一个月后的感想
作者分享了学习Clojure一个月的体验,将其与Common Lisp和Scheme进行比较,并赞赏其一致性和务实设计。
就用Go
一篇带有强烈观点的开发者文章倡导使用Go编程语言,强调其简洁的语法、强大的标准库、高效的并发模型以及单二进制部署,作为对过于复杂的现代技术栈的实用替代方案。