Hara - Clojure Inspired Micro Runtime

Hi Guys,

Wanted to share a project that’s been dear to me. So I’ve written a lisp. Not clojure but very similar and very performant.

The inclass benchmarks are here Hara Benchmarks . It’s be great if people can try it out and give feedback on the design / documentation etc.

Chris.

Hara: a Clojure-shaped Lisp for the WebAssembly era

Hara’s goal is not to replace Clojure, nor merely to reproduce Clojure syntax on another runtime. It is an attempt to preserve the parts of Clojure that give it unusual leverage—data-oriented programming, functional composition, macros, protocols and interactive development—while redesigning the execution substrate around three priorities:

1. speed and predictable runtime cost;

2. WebAssembly as a native execution environment;

3. a recognisably Clojure-like programming model.

A useful shorthand is:

> What might a Clojure-family language look like if it were designed today around WebAssembly rather than inherited from the JVM?

1. Clojure-like, without treating JVM history as language semantics

Hara should feel immediately intelligible to a Clojure programmer. Programs are built from ordinary data. The language favours immutable values, namespaces, higher-order functions, macros and small composable abstractions. Code should remain inspectable, transformable and suitable for an interactive Lisp workflow.

The objective, however, is conceptual continuity rather than complete compatibility.

Some aspects of Clojure are excellent language ideas; others are consequences of fitting those ideas onto Java classes, interfaces, allocation behaviour and host interop. Hara tries to retain the former without making the latter permanent requirements of every implementation.

This means Hara is not intended to be “Clojure with a few syntax changes.” It is closer to a re-examination of the boundary between:

values and their representations;

collections and their traversal;

language protocols and host interfaces;

portable programs and environmental capabilities;

interactive Lisp semantics and an efficient compiled runtime.

The standard is not whether every existing Clojure program runs unchanged. The standard is whether a Clojure programmer recognises the model, while gaining a smaller and more portable execution environment.

2. Speed as an architectural concern

“Fast” in Hara does not only mean winning long-running numerical benchmarks. For the environments Hara targets, performance includes:

low startup latency;

a small runtime and deployment footprint;

reduced allocation during ordinary collection processing;

predictable dispatch and iteration;

efficient compilation to WebAssembly;

acceptable performance before a large VM has warmed up.

Clojure obtains enormous expressive power from its uniform sequence abstraction, but that uniformity can impose costs. A simple traversal may pass through sequence wrappers, lazy sequence machinery and host objects even when the underlying operation is just a loop over contiguous data.

Hara’s aim is to preserve high-level operations such as mapping, filtering and reduction without requiring every traversal to materialise a list-shaped or sequence-shaped view.

The ambition is not to make the programmer manually optimise every loop. It is to give the compiler and runtime a cleaner representation from which efficient loops can be generated.

3. A pure iterator model instead of universal ISeq

One of Hara’s most important departures is its treatment of iteration.

Clojure’s ISeq family elegantly provides a common vocabulary over many collections, but it also combines several concerns:

a mechanism for traversal;

a persistent list-like interface;

a representation of laziness;

a universal view over collections;

the return type of many sequence operations.

Hara separates the value being traversed from the act of traversing it.

An iterator is only an iterator. It is not required to pretend to be a list, become the identity of the collection, or serve as the universal intermediate representation for every collection operation. Iteration state is explicit, while the original collection retains its own identity and representation.

“Pure iterator” here means that the abstraction has one responsibility: describe traversal. It is not also the language’s universal collection interface.

This separation should make it easier to support:

allocation-light loops;

specialised traversal over arrays, maps, strings and host memory;

lazy or streaming computations without conflating them with persistent lists;

clear distinctions between reusable values and traversal state;

compilation to WebAssembly’s comparatively simple memory and control-flow model.

The familiar functional vocabulary can remain, but it no longer has to be implemented through a universal seq conversion at every boundary.

4. Protocols as the organising principle

Protocols are not merely an interop feature in Hara. They are intended to be the main way the language and its libraries describe behaviour.

Where possible, Hara should prefer:

a small semantic protocol;

multiple independent implementations;

explicit composition;

minimal privileged compiler machinery.

Collections, iterators, callable values, encoders, workflows, capabilities and host integrations should be able to participate in the language through protocols rather than inheritance trees or a steadily growing collection of compiler-recognised special cases.

This has two consequences.

First, the language remains open to extension. A library-defined value should be capable of participating in ordinary language operations without having to become a built-in type.

Second, the runtime remains modular. The same surface abstraction can have different implementations in a browser, an edge worker, a server process or an embedded environment. Protocols become the stable semantic boundary; the runtime implementation behind that boundary can change.

The goal is to take one of Clojure’s strongest ideas—small, open behavioural interfaces—and push it deeper into the architecture.

5. WebAssembly is the native target, not an export format

Hara treats WebAssembly as a primary execution model rather than an optional compilation target added after the language has already been designed around another host.

That should allow the same Hara kernel to operate in:

browsers;

server and edge runtimes;

command-line tools;

sandboxed plugin systems;

embedded or application-specific environments.

WebAssembly provides a portable instruction format, but portability requires more than emitting .wasm files. A language also needs a coherent model for memory, modules, linking, values and access to the outside world.

Hara therefore aims to make environmental capabilities explicit. Files, networking, clocks, databases, user interfaces and other host services should be supplied through defined boundaries rather than silently assumed to exist as global runtime facilities.

The larger goal is a program that can move between hosts while retaining the same core semantics, with only its capabilities and integrations changing.

The intended result

Hara succeeds if it provides the semantic leverage of a Lisp, the data-oriented familiarity of Clojure, and the deployment characteristics of a small WebAssembly-native runtime.

It is not an argument that Clojure chose the wrong abstractions. Clojure was designed brilliantly for its host and its time. Hara is an exploration of which of those ideas remain essential when the host changes—and which parts can now be simplified.

In one sentence:

> Hara is a protocol-oriented, WebAssembly-native Lisp that keeps Clojure’s data and programming model while replacing its JVM-shaped sequence and runtime foundations with smaller, more explicit abstractions.