Glojure: A Hosted Clojure Interpreter for Go

Glojure is a hosted interpreter for Clojure implemented in Go. It enables developers to write Clojure code that has direct, seamless access to Go libraries, mirroring the relationship Clojure has with the Java Virtual Machine (JVM) and Java frameworks.

Key Technical Architecture

Hosted Language Design

Glojure is designed as a "hosted" language, meaning it is implemented in terms of its host language (Go). This architecture ensures that all Go values can be used as Glojure values and vice versa, allowing for high-fidelity interop between the two languages.

Execution Model

Glojure utilizes a tree-walk interpreter. While this approach allows for rapid development and flexibility, it is noted as having limited performance compared to bytecode interpreters or compiled languages.

Go Interop and Standard Library Support

Glojure provides out-of-the-box interop with a wide range of Go standard library packages. To avoid conflicts with Clojure's namespaced symbols (which use /), Go package names containing slashes are replaced with colons (:).

Default supported packages include:

  • fmt, os, io, net/http (accessed as net:http), time, sync, context, reflect, and regexp.

For developers needing access to additional Go packages, Glojure provides a tool called gen-import-interop. This tool generates a Go file that exports a function to add specific package exports to the Glojure package map, allowing custom Go libraries to be integrated into the Clojure environment.

Usage and Deployment

Standalone Tooling

The glj command-line tool provides a full Clojure development experience, including a REPL with Vi and Emacs editing modes, multiline editing, tab completion, and persistent history. It supports evaluating expressions directly via the -e flag or running .glj scripts as standalone programs.

Embedding in Go Applications

Glojure can be embedded as a scripting language within Go binaries. This allows developers to add scriptable configuration, create plugin systems, or mix Go's performance with Clojure's expressiveness.

Integration example:

package main

import (
    "fmt"
    "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)
}

Comparison with Other Go-based Clojure Implementations

Aspect Glojure Joker let-go
Hosted Yes No No
Extensible Go Interop Yes No No
Concurrency Yes Yes (GIL) Yes
Tooling (Linter) No No No
Execution Tree-walk Interpreter Tree-walk Interpreter Bytecode Interpreter

Data Type Mapping

To maintain compatibility between Go and Clojure, Glojure maps types as follows:

  • Integers: long maps to int64, int maps to Go's platform-dependent int.
  • Floating Point: double maps to float64, float maps to float32.
  • Bytes: byte maps to Go's unsigned byte (differing from JVM's signed bytes).
  • Arbitrary Precision: BigInt and BigDecimal wrap *big.Int and *big.Float respectively.
  • Characters: char is implemented as a tagged rune (type Char rune).

Community Insights

Community discussion highlights that Glojure is viewed as a promising implementation due to its focus on proper interop. However, some users have noted the lack of of security or sandboxing features for embedded use cases, and the potential performance overhead inherent to tree-walk interpretation.

Sources