Pure functions are back on the menu: how Replicant revives the Elm Architecture

..

This article is currently a DRAFT UNDER REVIEW. Please do not share the link with other people quite yet.

Sections, words and references may be missing. Formulations may be weak, vague or conflicting. Words may suffer from severe misspellings and other misuse.

Elm was and is an amazings way to write user interfaces that run on web browsers. Today, those interfaces are more commonyl written with React and Typescript. Why, you ask? A source of friction for Elm usage was the walls of Elm’s typed garden.

Browser APIs like HTML and SVG mapped neatly to typed functional interfaces, and got great bindings in elm/html and elm/svg. Less pure browser APIs like Canvas and Audio didn’t get the same love, and programmers had to resort to ports.

2025 Elm’s core ideas back to life in Replicant — The Elm Architecture rediscovered and reimagined, this time not suffering from the typed garden walls.

As a React/Typescript dev, you may find ideas to steal that let you worry less about hooks, and test more of your code. As a Clojure(script) dev, you may gain a new hammer you already know how to use (functions returning hiccup), with more emphasis on data than what you might otherwise build with Reagent.

Before we dig into Replicant’s innovative ideas, I’m going for a detour down memory lane - my joyful experience of finding Elm.

The Zen of Purely Functional UI with Elm

Focus - get the UI done.

Look at this beauty:

-- App.elm
-- ....

The Elm Architecture - avoid pitfalls & keep the code workable. Effects - great code & great productivity as long as you can and will stay within the typed garden.

What could Elm have been?

Thinking back on Elm fills me with a certain melancholy. Elm was an improvement on its predecessors and its successors.

I wish Evan could have gotten the funding to finish what he started. But open source funding is hard, and the big infrastructure players tend to capture the value.

I’m grateful to Evan and the rest of the Elm team for the effort they put in. You all helped me learn programming, user interfaces, pure functions, type systems and teaching.

How Replicant revives The Elm Architecture

For nine, long years, I’ve longed to return to the simplicity and beauty that Elm brought to the table. Here’s the counter, again:

-- App.elm
-- ....

And here, in 2025, I’m delighted to say that purely functional user interfaces are back on the menu!

Looks like purely functional user interfaces are back on the menu, boys!

Here’s the same browser, in Clojurescript with Replicant:

;; init.cljs
(replicant.render ,,,,)

;; ui.cljc
(defn counter (count) ,,,)

The astute reader will notice explicit mutation in Clojurescript, and the absent of such explicit mutation in Elm. In Clojurescript, we swap! on a state atom. In Elm, however, the state changes are tacit.

Beyond The Elm Architecture: what Replicant brings to the table

I want to describe how Replicant improves upon The Elm Architecture, but I'm not sure how to do it. It may be out of scope for one text.

Extensible imperative machinery

Beyond The Elm Architecture: extensible imperative machinery, DOM tree as data, events as data and UI code that runs on the frontend and the server

HTML as data

Elm treats DOM nodes as a type, HTML.Html. Replicant treats DOM nodes as data structure, vectors, lists and maps.

Leverage: tests. Example: tree seq.

Events as data

auto-serialization

Leverage: auto-serialization, tests. Example: collect all events for debugging.

UI functions can run on the backend too

Leverage: no browser needed for unit-testing UI functions

Sections that maybe should be included

A "collection box" for things I wanted to write, but don't fit (yet). Could be removed, or put into the text somewhere.

Types: to help the developer write code, and to inform system design

Elm is typed, Clojurescript is not. I’d like to highlight two advantages of Elm’s types:

Types & Repliant

https://worrydream.com/refs/Moseley_2006_-_Out_of_the_Tar_Pit.pdf

Component local state: necessary for performance?

Should we do a new VDOM comparison and re-render each time the user moves the cursor? Maybe not. We can avoid those extra re-renders in React with component local state, and signal changes to the rest of the app when we’re ready.

So it’s impossible to have a pure UI if we need performance like this, right?

No!

We can gain this exact benefit and keep our lovely Elm Architecture. Enter Replicant Aliases. Under the hood, a browser textbox has a value. We apply our events as data trick, and tada, the UI is pure again:

[:textarea {:on {:change [:set-user-name]}}]

Our job as imperative machinery-programmers becomes to write “the textarea we need”. The “textarea we need” could be a declarative interface to Mapbox (which exists), or a declarative interface to CodeMirror (which per 2025-04-18 does not exist, I think).

The Replicant Alias for Mapbox adapts Mapbox’s imperative API to play nicely with an otherwise Purely functional UI. For the FP nerds out there, we need to translate component argument changes as Coeffects into imperative Mapbox function calls, and listen to events (effects) from Mapbox, translate to data, and pass the data to Replicant.