fumola

An experimental programming language whose execution model is Adapton: a program's run leaves behind a graph of what it named, read and demanded, and that graph is what the next run reuses.

github.com/Adapton/fumola the source, the Fumola library, and the issues

This host also serves the Fumola runtime compiled to WebAssembly — fumola_wasm.js and fumola_wasm_bg.wasm, in this directory. It is what the Hazel integration can load, and what the box below is running.

The Fumola-Hazel integration is served here too — a live build of Hazel's experimental-lang-integration branch, loading that same runtime same-origin. What is in it is below.

There is also a web playground: the same runtime full-screen, with the library beside you, syntax highlighting, and no button to press.

The language#

Fumola is two things joined. Its surface is conventional and borrowed: functions, records, variants, modules, pattern matching, arithmetic — taken from Motoko, whose Rust interpreter Fumola descends from. Those features are deliberately ordinary, so that they stay out of the way.

What is not ordinary is how a Fumola program runs. Its incremental primitives come from The Extended Adapton Recipe, including Demanded Computation Graphs extended with symbolic space and time.

public func gcd(a : Nat, b : Nat) : Nat {
  if (b == 0) { a } else {
    gcd(b, a % b)
  }
}
footnote: nothing incremental here

That is the whole of Gcd, one of the library modules compiled into the runtime. Nothing about it is incremental — it reads no cells, so there is nothing for Adapton to reuse. The primitives are what you reach for when you want a computation to survive being edited.

Adapton, briefly#

Adapton is a way of running a program so that changing its input does not mean running it again. As the program executes it builds a demanded computation graph: a bipartite record of cells, which hold values, and thunks, which are suspended computations, with an edge for every action a thunk took — every cell it wrote, every cell it read, every thunk it forced — kept in the order it took them.

The graph exists so that a later run can be repaired rather than redone. When a cell changes, the thunks that read it are the ones whose results are in question; everything else in the graph is still good, and is reused as it stands. What makes this addressable at all is that every cell and thunk has a name: the run after the edit has to recognise the node it is about to recompute as the same node as last time, and a name is how it does.

footnote: repair, so far

Repair is where Fumola's own work is. Since 2026-09-08 the graph realigns after an edit: a put that changes a cell with readers signals the edges that observed it, and a force of a thunk whose trace holds a signaled edge repairs it — edge by edge, in the order the thunk took them, re-evaluating only when an edge cannot be realigned. Both traversals are written into the event history as they happen. /alignment/ explains it, with the Recipe's rules beside the prose. What is still to come is listed at the end of this page.

The primitives#

There are not many. Two ways to make a node, two ways to depend on one, one way to look without depending, and a way to say where the nodes you make should live.

thunk { e } Suspend a computation. A thunk is an ordinary Fumola value: it can be bound, passed and stored.
`n := e put. Write the value of e into the cell named `n, and evaluate to a pointer to that cell. Putting a thunk is how a named thunk comes to exist.
pointer(`n) The pointer that the symbol `n names, whether or not anything has been written there.
@ p    get(`n) get. Read a cell, recording a dependency: an edge from the thunk doing the reading to the cell read. get(`n) is @(pointer(`n)) — note the space in @ p, and the brackets here.
force(p) force. Demand the thunk at p, recording a dependency on it. A cache hit answers with what that thunk already computed; a cache miss runs its body.
peek(`n) Read a cell without recording anything. Answers an option, since the cell may never have been written.
do within space `s { e } Navigate. Names the region that the cells and thunks allocated inside e belong to, so that a subcomputation can allocate freely without colliding with its callers.
force(`n := thunk { e }) Not a primitive but the idiom the others are usually reached through: name a thunk, put a computation in it, demand it. Running it again with the same name is what an edit is.

Two things to know about writing @. It must be separated from what it reads — @ p, not @p — because an identifier may begin with @, so @p is a single name rather than a read of p. Those names are reserved: Fumola inherits them from the Motoko lineage its syntax comes from, where a program may use the ones the language defines but never define its own. @here and @now, which Fumola adds, are exactly that. And @ takes one nullary expression, so reading the result of a call needs brackets: @(pointer(`n)), not @ pointer(`n).

footnote: why these colours

The Fumola VS Code theme paints := and @ in their own color, distinct from every other operator. Put and get are two of the effects the graph is a record of.

Names are values#

A name in Fumola is a symbol, and symbols are first-order data. An identifier is a symbol, written with a leading backtick; a number is a symbol; and symbols compose by application, as deeply as you like. So a name can be computed — which is what lets a recursive algorithm give each of its nodes a name derived from its position rather than from a counter.

`hello                 // an identifier
7                      // a number is a symbol too
`adapton(`settings)    // application, and it nests

Space and time#

The Recipe's extension is that a name has two dimensions, not one. Space is where a cell lives: put `x inside the region `region and the cell allocated is `region(`x), so a subcomputation can name things freely without colliding with its caller. Time is when it holds what it holds, which is what lets one name carry a history rather than a single value. Each is mostly just a symbol, and each has a relative form for the place evaluation happens to be:

Adapton.here()         // the space we are in now
Adapton.now()          // the time we are at now
Adapton.space(`region) // a space named outright

Two modes#

A Fumola program runs in one of two modes, and which one it is in is decided by whether a force is underway.

The archivist runs inside a force. Its operations are the tracked ones — put, get, and force itself — because there is a computation in progress for a dependency to be recorded against. An edge recorded while a computation is in progress belongs to that computation; a put or get made outside a force is recorded against the graph's root instead.

The editor is the more general mode, and always runs on a force-free stack. It is where changes come from. Resetting the store and reading the graph belong to it alone: a reset from inside a force pulls the store out from under the computation making it, and nothing in the runtime prevents that.

peek belongs to the editor, but it stays available inside a force on purpose, because reading a cell without recording a dependency is exactly how you look at a computation while it runs. A peek in a thunk is meaningful; it is merely untracked.

Two semantics: graphical and simple#

The primitives above have two implementations in the runtime, and an instance runs one or the other. Graphical is the default — it is what the VM constructs, what the REPL starts in, and what the examples throughout the repo run.

Graphical keeps the DCG proper: nodes, edges, each thunk's trace in the order it ran, and an event history stamped with a meta-time. It is the semantics the pictures on this page are drawn from, the one the introspection below reports on, and the one repair runs on.

Simple keeps a store and no more: cells indexed by space and time, thunks with their cached results. Forcing a thunk that has already run answers with what it computed. It is predictable and cheap, and it keeps no graph, so there is nothing to introspect and nothing to repair. It has to be asked for by name — which the Hazel integration currently does, for a reason given below.

The graphical store reports on itself in Fumola's own value language, so a program can read its own history as ordinary data:

// the event log: addNode, addEdge, forceBegin, forceEnd, signalingBegin, repairBegin, ...
Adapton.peekEvents()    // [{metaTime; event}]
Adapton.peekInfo(p)    // one node, and the edges either side of it
Adapton.peekHistory()   // events, nodes and edges together

Because the graph is readable as data from inside the language, a Fumola library can process its own past behavior into something to look at — the outlines, scenes and JSON exports in fumola/system/adapton — without any of it being built into the runtime.

Try it#

The runtime this host serves, running here. One graphical instance — Fumola's default — that persists across every program you run below, so a cell written by one is still there for the next. That persistence is what makes an edit an edit rather than a fresh start.

Take a named thunk, change the 21, and run it again. Same name, same thunk, re-forced — which is exactly what a Fumola program inside Hazel does to itself as you edit it.

This box is deliberately small. The web playground is the same runtime with room to work: the library on the left to read as you go, highlighting from the language's own lexer, and evaluation as you type rather than on a button.

the runtime loads when you first run something

mergeSort, drawn as a graph#

Demand-driven mergeSort on 44 unsorted elements, drawn as its DCG. The input list is the column of green boxes on the left; a balanced level tree over those elements is built beside it; the blue orbs are thunks, and the edges leaving each one are the actions it took. The long horizontal run through the middle is the stream of merge thunks producing the sorted output, extending rightward only as far as the output was demanded.

lazyMergeSort on 44 elements, drawn as a Demanded Computation Graph.
Interactive version — the replayground, which lets you move through the graph and inspect the data held in it. The scene it plays is generated by Fumola code reading its own event history.

Fumola in Hazel#

Hazel is a live functional programming environment: every edit state has a meaning, so a program has a result even while it is incomplete. It has livelits — user-defined graphical literals embedded in the program text, which expand to the expression they stand for.

The two languages are close relatives — both ML-family, both with variants and records and thunks, both having made evaluation order a design question rather than an accident — and they hold opposite halves of the same idea. Hazel has liveness: a cell shows a value while the formula is half-written. Fumola has dependency: a cell knows who read it, and a change reaches exactly those readers. Hazel re-runs and forgets; Fumola remembers and cannot show anyone. That is why putting them together is interesting rather than redundant.

The current integration is one builtin livelit, ^fumola_wip: a Fumola program written as Hazel tiles, running in the editor or archivist role, with a watch pane for its instance beside the code. It has a live build. The Hazel integration page describes it, and the earlier attempts that led to it:

footnote: two layers meeting, and what the instance turned out to measure

Two incremental systems meet here, and how they compose is still the open question: Hazel re-evaluating a program as it is edited, Adapton repairing a graph underneath it. Three disciplines are written down as candidates, and the preferred one is the least invasive: that the composition is a discipline on the Fumola program — the archivist operations are already idempotent, so being re-run arbitrarily is safe — rather than a change to Hazel's evaluator. Preferring it is not establishing it.

It also inverted what the work is for. A Fumola instance is the first state reachable from a Hazel program that Hazel's evaluator does not control, which makes it the first thing that can count the evaluator's own effects. A Hazel probe tells you what your program computed; a Fumola instance tells you what Hazel did. Incremental computing was supposed to be the payload, and turned out to be the microscope.

Both halves of that are written up as design notes against Hazel's own repository, in pull request #2565: Notebook semantics, the instrument pointed at Hazel's evaluator — what actually runs when you edit, and the two milestones that would turn the observations into a measurement — and Programmable probes, the same instrument pointed at a user's own program. Both are proposals, and both are careful to say which of their numbers are demonstrations rather than measurements.

Publications#

Fumola's primitives come from two papers in particular. Between them they are the primitives above: the first is the machinery, the second is what makes it addressable.

footnote: the earlier lineage

Further back: Adapton descends from self-adjusting computation, whose root is Adaptive functional programming (Umut A. Acar, Guy E. Blelloch, Robert Harper, POPL 2002).

Hazel#

The environment the integration above plugs into, and the mechanism it plugs in through.

Press about Fumola#

Writing about this work from outside this repository, on its own page: Press. It is mostly Week in Hazel, a weekly digest that has covered the Fumola integration, so the list grows on its own schedule rather than this page's — which is why it is a page and not a section here.

Most recent: Two languages. One notebook. Week in Hazel, issue 001 · September 4–10, 2026

Drafts#

A line of work on typing these primitives, rather than only running them. If names decide what gets reused, then using a name twice for two different things is a bug the type system ought to catch — and the effects a computation has on the graph are exactly what a type-and-effect system is for.

Drafts, and they stayed drafts — neither the theory nor the implementation was finished at the time.

Human-Fungi Interface#

Something did get built, though. Human-Fungi Interface, by Ryan Vandersmith, was the web interface to Fungi: it pretty-printed the code and its traces, highlighted variables, ran the program in the browser, and drew the type-and-effect derivations themselves — a renderer per judgement form, for effects, kinds, index sorts, name terms and contexts. The drafts above are about typing these programs; that was where you went to see the types. Its first commit is dated the day before the Fungi implementation's own.

Its build instructions say rustup target add wasm32-unknown-unknown — the same line this repository's publish workflow runs. Both are the same arrangement: a Rust incremental runtime compiled to wasm, executing in a browser, with the interface reading the trace back out and drawing it. What has changed is the setting. Hazel stands where the hand-written JavaScript was, and a program that can read its own event log stands where the trace pretty-printer was. Its demo is gone — the free host it lived on stopped existing, and no archive caught it — so the source is what remains of it, and the Hazel integration is the project taken up again. The reason to take it up is not only that it was unfinished. It was a pleasure the first time.

Abstract interpretation as another lens (beyond just types)#

In 2025, conversations with Bor-Yuh Evan Chang at CU Boulder pointed to abstract interpretation as a second way in. The drafts above approach the graph through types alone; taking the two together is meant to reach more of it — the dynamics as well as the statics, and the patterns of use a DCG falls into. His Fall 2025 graduate seminar there, Program Analysis: Theory and Practice (CSCI 4830/7135), is where that framing is being worked out. The two have been at the neighbouring problem before, in A vision for online verification-validation (with David Van Horn, GPCE 2016).

footnote: checking, made incremental

Type-and-effect checking a Fumola program is itself a computation over a structure that is edited a little at a time — which is the shape of problem Fumola exists to make incremental. So the checker is not just a thing to build for Fumola; it is a thing to build in it.

Three ways of looking#

Three interfaces to the same idea, eight years apart. Each has had something the other two lacked, and none has yet had everything.

Ryan Vandersmith and Matthew Hammer met at CU Boulder, in an experimental class on the principles of functional programming that followed course material from Ravi Chugh — who is also a co-author on two of the Hazel papers this work is built on.

2018 — the derivations#

Human-Fungi Interface put a Fungi program in a browser and drew what the type system knew about it: the expression, its type, its effects, and the kinds, index sorts and name terms underneath — then the trace it produced when it ran. It is the only one of the three that has shown the derivations. It could not let you change the program: it read bundles generated ahead of time.

Now — the graph#

The replayground draws a whole run as a scene you move through — the 44-element mergeSort above, every cell and thunk and edge of it, at a scale a derivation tree could not show. It shows what a computation did, which is the thing the 2018 view could only show one step at a time. It cannot let you change the program either: it is a static file exported from a demo, and live editing is still listed under future work.

Next — both, and live#

Hazel is the one where the program is editable and still means something while you are halfway through typing it. That is the missing half of both earlier views, and it works today: Fumola runs as you type and hands the result back as a Hazel value. Since the tiles route, the store is legible while you edit it, too — the panel gives the nodes, the edges and the events as three views of one history, and a node id is a link you can follow.

What is still missing is the drawing. Three views of a graph are not a graph; nothing here yet lays a run out in space the way the replayground does, and nothing draws a type the way the 2018 interface did. So: the first had the types and not the editing, the second has the picture and not the editing, the third has the editing and the store in readable form, and wants the picture next.

footnote: the intervening years

Most of that time went elsewhere. Two children arrived along the way; a career entirely academic until January 2019, then a decision to leave academia for four and a half years at DFINITY and then a return to independent research. This repository's history starts in July 2022, and it starts as Motoko's Rust interpreter: the industry work is the substrate Fumola runs on. Every crate this repository inherited from that interpreter still carries the same two authors it did — Matthew Hammer and Ryan Vandersmith. The newest crate carries a single name: the one that compiles Fumola for a browser, which is what runs the console above.

The runtime this host serves#

Alongside this page: fumola_wasm.js, the wasm-bindgen glue, and fumola_wasm_bg.wasm, the runtime. Both are built from the fumola_wasm crate by .github/workflows/wasm-pages.yml on every push to main, and neither is checked in.

Published as a Pages site rather than as a release asset, deliberately: release downloads carry no Access-Control-Allow-Origin header, so a browser can never fetch one cross-origin — which is the only thing this artifact exists to do. Pages serves it with CORS and the right content type.

// The current build. runtime.json names a content-addressed
// directory, so a new publish is a new URL and never a stale cache.
const m = await (await fetch("https://fumola.org/runtime.json",
                        { cache: "no-cache" })).json();
const { default: init, fumola_create, fumola_eval_top } =
  await import("https://fumola.org" + m.js);
await init({ module_or_path: "https://fumola.org" + m.wasm });

const id = fumola_create();
fumola_eval_top(id, "`x := 41; get(`x) + 1");
// -> {"ok":true,"tag":"Int","value":"42"}

To pin a build instead, skip the manifest and name the version directly — /v/<hash>/fumola_wasm.js and its .wasm sibling. Those bytes never change. Take the glue and the binary from the same directory: wasm-bindgen emits them as a matched pair and they will not load if their versions disagree. Recent versions are listed in versions.json; only a bounded number are kept, so a pin is good for the near term rather than forever.

The stable fumola_wasm.js and fumola_wasm_bg.wasm beside this page still work and are always the current build at publish time. They are what a browser can serve you a version behind, which is what the manifest exists to avoid.

Fetch them from fumola.org, which is where this site lives. The older adapton.github.io/fumola path still works for a link, but it now redirects here, and a Pages custom-domain redirect carries no Access-Control-Allow-Origin — so it can no longer be fetched cross-origin, which is the one thing this artifact is for.

The web playground on this host loads them the same way, from a relative path rather than across an origin.

The glue and the .wasm are generated together and will not load if their versions disagree, so take both from the same place. Hazel prefers a locally built pair when there is one and falls back to this published build otherwise.

What is happening now#

Work in progress, written 2026-09-08 and meant to be rewritten as it moves. Two things changed this week: the graph learned to realign after an edit, and the words for that were settled.

The words#

Fumola's terms for the repair algorithm differ from the Adapton papers' — deliberately, in terms and not in algorithms. The papers say dirty and clean; those describe a state. Fumola says signaled and aligned: the first says what happened to an edge (a message reached it, and it waits to be checked), the second is already the Recipe's word for a store or a graph that agrees with itself. The change is being made everywhere — runtime, library, documents, and since 2026-09-09 the Recipe's rules — and is not finished; where you still meet the old words, they mean these.

The papers' words, and Fumola's
dirtyingsignaling — the traversal an edit starts, from the changed cell toward its readers
dirtysignaled — an edge whose recorded observation may no longer agree with the graph
cleanaligned — an edge that does agree, or a whole node all of whose edges do
cleaningrepair — the traversal a force runs on a thunk with a signaled edge: check each edge, re-evaluate if one cannot be realigned
change propagationrealignment — the whole, from an edit to the next consistent demand; signal and repair steps, usually in matching pairs

What landed#

Signaling on a put that changes a read cell, repair on the next force, and a matched put — putting what a cell already holds keeps the cell and its readers as they are. Both traversals are bracketed in the event history, so a program, the playground and the replayground can see a repair happen rather than infer it from a diff of two runs. The runtime this host serves carries it.

What is next#

In the engine: a cycle check, signaling on delayed puts, and the double-use check of Nominal Adapton. In the tools: a playground example that is one graph edited in place, with the trails drawn from the signaling and repair walk. In the measurement: an oracle that runs the existing comparative examples as repairs and checks them against a from-scratch run, and sweeps that were too expensive when every sample cost two full runs. In the Recipe: the words, a few missing rules, and the typos that surfaced when its rules were lined up with the code. The full list is in the docs.