AIArticle
A new interpreter enables seamless, bi-directional interop between Clojure and Go, opening new scripting and embedding possibilities.
Clojure has always been defined by its hosted nature. On the JVM, it leverages Java's vast ecosystem; in the browser, ClojureScript targets JavaScript. Now, an open-source project called Glojure is bringing this hosted philosophy directly to Go.
Currently at version 0.3.0, Glojure is an interpreter for Clojure hosted on Go. Unlike traditional language ports that run in isolated sandboxes, Glojure is built to be a first-class citizen of its host environment. This design choice allows for seamless, bi-directional interop where Go values can be used directly as Glojure values, and vice versa.
The Mechanics of a Hosted Interpreter #
Many alternative language implementations on the Go runtime operate as isolated interpreters with rigid boundaries. Glojure takes a different path by implementing its types and evaluation model in terms of Go's type system. This approach allows developers to access Go libraries directly from Clojure code, mimicking the way standard Clojure interacts with Java frameworks.
For example, a developer can write a fully functioning HTTP server in Glojure that directly invokes Go's standard net/http
library:
(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)
Because of the hosted nature of the language, the Glojure runtime handles the translation of namespaces and method invocations into Go's underlying package and function calls.
Interactive Development and REPL Features #
For Clojure developers, the Read-Eval-Print-Loop (REPL) is the center of the development workflow. Glojure provides a standalone command-line tool, glj
, which boots into an interactive REPL equipped with several quality-of-life features:
Editing Modes: Supports both Vi (the default) and Emacs editing modes, configurable via~/.inputrc
.Multiline Editing: Incomplete expressions automatically continue on the next line with smart auto-indentation.Tab Completion: Offers context-aware completion for symbols, namespaces, and aliases with descriptive labels.Persistent History: Saves command history to~/.glj_history
across active sessions.UX Conveniences: Supports bracketed paste for pasting large code blocks instantly, job control (Ctrl+Z
to suspend,fg
to resume), and interrupt handling (Ctrl+C
to cancel input or halt evaluation).
Beyond the interactive REPL, the glj
tool can execute expressions directly from the command line using the -e
flag or run standalone Clojure scripts.
Serverless Inference by DigitalOcean 55+ models, every modality. One API key, one bill.
Embedding Glojure in Go Applications #
One of the most practical use cases for Glojure is embedding it as a scripting engine within existing Go applications. This allows developers to expose a highly expressive, dynamic language to users for writing plugins, defining complex configurations, or extending application behavior without recompiling the host binary.
To embed Glojure, developers import the runtime and evaluate Clojure code directly within Go:
package main
import (
"fmt"
_ "github.com/glojurelang/glojure/pkg/glj" // Initialize Glojure
"github.com/glojurelang/glojure/pkg/runtime"
)
func main() {
result := runtime.ReadEval(`
(defn factorial [n]
(if (<= n 1)
1
(* n (factorial (dec n)))))
(factorial 5)
`)
fmt.Printf("5! = %v\n", result) // Outputs: 5! = 120
}
Bi-Directional Interop
Passing data and functions across the language boundary is straightforward. Go functions can be registered as Glojure vars, and Clojure functions can be invoked directly from Go using the Invoke
method:
package main
import (
"fmt"
"github.com/glojurelang/glojure/pkg/glj"
"github.com/glojurelang/glojure/pkg/runtime"
)
func greet(name string) string {
return fmt.Sprintf("Hello, %s from Go!", name)
}
func main() {
// Expose a Go function to Clojure
runtime.ReadEval(`(def greet-from-go nil)`)
greetVar := glj.Var("user", "greet-from-go")
greetVar.SetRoot(greet)
// Execute it in Clojure
result := runtime.ReadEval(`(greet-from-go "Clojure")`)
fmt.Println(result) // Outputs: "Hello, Clojure from Go!"
// Invoke a Clojure function from Go
runtime.ReadEval(`(defn add [x y] (+ x y))`)
addFn := glj.Var("user", "add")
sum := addFn.Invoke(10, 32)
fmt.Printf("Sum: %v\n", sum) // Outputs: Sum: 42
}
Developers can also expose custom Go packages or standard library packages to the embedded interpreter using a package map approach, granting Clojure scripts access to internal application APIs.
Current Status and Getting Started #
Glojure is in early development. The maintainers caution that users should expect bugs, missing features, and limited performance at this stage. Backwards compatibility is not guaranteed until the project reaches a v1
release. However, it is already capable of running a significant subset of a transformed core Clojure library and has been used successfully in hobby projects.
To get started, you need a working Go installation (version 1.19 or higher is recommended for general knowledge, but installing from source requires at least Go 1.24). You can install the command-line tool using Go's installation toolchain:
$ go install github.com/glojurelang/glojure/cmd/glj@latest
Once installed, running glj
will drop you directly into the interactive REPL, ready to bridge the gap between Clojure's expressive functional paradigm and Go's robust runtime.
Sources & further reading #
Clojure Hosted on Go— github.com
Priya Nair· AI & Developer Experience Writer
Priya covers AI frameworks, developer productivity tooling, and the startup ecosystem across South and Southeast Asia, bringing a researcher's rigour and a practitioner's empathy to every story. She is deeply sceptical of benchmarks and asks hard questions so her readers don't have to.
Discussion 2 #
i've been experimenting with glojure and it's really interesting to see how it enables bi-directional interop between clojure and go, the fact that go values can be used directly as glojure values is a total game changer 🤯
i'm intrigued by the idea of glojure but i'd love to see some concrete benchmarks on the performance overhead of this interop, the article mentions 'seamless' integration but what does that really mean in terms of execution speed?