Skip to content
Hara

Hara for Clojurists

Clojure developers already understand one of Hara’s central ideas: a program does not have to stop before you can work on it. You can inspect a running process, evaluate a definition, replace a function, and continue from the current state.

Hara builds on that tradition, but gives it a different centre. It is an embeddable live kernel for building, inspecting, and changing running systems. HAL forms are how you communicate with that kernel.

It is not intended to reproduce all of Clojure on another runtime. Hara is deliberately stripped down: small enough to embed in a browser, compile to WebAssembly, run locally, and expose safely to tools and AI agents. Portability keeps the kernel honest; the main idea is simplicity.

Begin with what you already know

The surface is deliberately familiar:

(def answer 42)

(defn add [a b]
  (+ a b))

{:name "Ada"
 :status :active}

So is explicitly held local state:

(def counter
  (atom {:value 0
         :updated-by nil}))

(swap! counter update :value inc)
(deref counter)

The REPL is one client of the kernel, not the complete definition of the development environment.

Information comes first

Hara begins with persistent information. A transformation produces a new value rather than silently changing the old one:

(def order {:id "order-42" :status :created})
(assoc order :status :submitted)

Clojure's persistent data structures make robust systems easier to build: an update produces a new value, leaving the previous value available for comparison, testing, tracing, recovery, and explanation. Tools can show what changed without reconstructing hidden mutation.

Atoms make changing state explicit

An atom is a useful, familiar way to hold explicit local runtime state. Hara does not require Clojure's full STM, refs, agents, or every concurrency primitive before you can model a changing system.

The aim is a direct model: state ownership is visible, transitions have names, and values before and after an update can be inspected.

Iterators, coroutines, and promises

Hara is iterator-first. It does not implement Clojure ISeq; a Seq is a frozen view on an iterator. Familiar operations remain familiar:

(map inc [1 2 3])
(filter odd? [1 2 3 4 5])
(take 5 (range))

But a source may be a persistent collection, a lazy range, a one-pass stream, or values produced by a coroutine. Do not assume that every source is replayable or has the same storage and resource semantics.

Promises represent one eventual result; coroutines represent resumable producers or workflows. Together they make asynchronous streaming explicit without requiring every Clojure concurrency abstraction or a mandatory transducer layer.

Sessions isolate runtime worlds

A kernel can host named sessions. Each session has its own evaluation context, namespaces, definitions, and atoms. The same namespace name can therefore have different values in different sessions without those worlds colliding.

Sessions solve a different problem from STM: STM coordinates shared state in one runtime world; sessions isolate definitions and state into separate runtime worlds.

From REPL-driven to runtime-driven development

REPL-driven development remains valuable. Hara uses the broader term runtime-driven development because several surfaces can work with the same live system.

A runtime-driven system makes answers visible: which state exists, which form changed it, which value crossed a boundary, and which effect reached the host.

Compatibility and measured comparison

Hara deliberately shares a useful Clojure-shaped core, but it is not a drop-in JVM implementation. The current canonical inventory compares Clojure 1.12.5 with Hara's portable core plus std.lib.foundation:

Group Core entries
Same exact name and contract 144
Same name, changed contract 12
Renamed 1
Hara-specific 87
Clojure-specific 536

The changed cases are the ones worth checking while porting:

Clojure Hara Practical difference
map map Direct calls on concrete collections are eager and preserve shape; curried and seq-wrapped calls are lazy.
seq seq Hara exposes an explicit iterator boundary rather than requiring ISeq.
rest rest Hara returns nil when no values remain; Clojure returns an empty sequence.
some any? Hara returns a Boolean existential result; Clojure returns the first truthy predicate result.
let let Both currently evaluate bindings sequentially.
eval eval Hara evaluates in the active Hara runtime context; it does not invoke the Clojure compiler.

The complete, exhaustive contract—including all changed and runtime-drift entries—is in Clojure core compatibility.

Startup evidence

The following measured startup snapshot is useful when portability and quick tool feedback matter. It was generated on Linux on 24 July 2026; it is machine-specific evidence, not a performance promise or regression target.

Runtime p50 ms p95 ms Peak RSS MiB
Clojure 463.67 488.21 110.1
Babashka 12.65 19.39 36.2
Hara native image 11.98 15.88 38.3
Hara Rust native 4.73 8.97 5.0
Hara WASM (Node) 37.00 39.36 62.9

Peak RSS MiB is the maximum resident memory observed for that process. The benchmark export also contains a Payload MiB field, but its artifact collection rule is not documented with the results. It is therefore omitted here rather than being mistaken for download, install, or memory size.

Warm evaluation evidence

Warm measurements show the current implementation cost—not a Hara speed claim. Values are steady milliseconds per complete evaluation; lower is better. Each adapter re-reads, evaluates, and checks the same source form, but this remains an implementation snapshot rather than a source-normalized language shootout. Hara's Rust and WASM interpreters are presently slower than Clojure and Babashka on these small, allocation-heavy forms, with function calls the clearest gap:

Workload Clojure Babashka Hara Rust native Hara WASM (Node)
Arithmetic 0.892 0.477 3.655 4.174
Function call 0.813 0.227 29.627 19.903
Persistent vector 0.507 0.119 0.927 0.962
Persistent map 0.452 0.110 1.087 1.234
Sequence navigation 0.450 0.086 0.563 0.617

See Runtime benchmarks for the complete workloads, first-call timings, calls per second, convergence data, and the other runtimes.

What carries across—and what to check

The safe rule is simple: bring across the conceptual model, confirm the exact operation, and write to Hara’s portable kernel contract.

The mental shift

Hara takes the live-development instincts of Lisp and treats them as the basis of an embeddable, inspectable kernel.

Choose a host in On the Web, or continue with the browser playground.