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.
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.
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:
- First: livelits, which proved a Fumola program can run in a live document and hand back a value.
- Then: tiles, which made the program a term in Hazel's grammar.
- And then: watching the store, a panel for the instance the cursor is in.
- The bet, and how it came out: a livelit that keeps the program as tiles.
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.
-
Adapton: composable, demand-driven incremental computation
PLDI 2014 · pp. 156–166 · doi:10.1145/2594291.2594324
Adapton itself. Demand drives the computation, the demanded
computation graph records what that demand touched, and the graph
is what a later run repairs.
thunk,force, put and get are this paper's. - Incremental computation with names OOPSLA 2015 · pp. 748–766 · doi:10.1145/2814270.2814305 Names, made explicit and first-class. This is the paper the symbols above come from — what it calls a name, Fumola spells with a backtick. Without them a second run has to infer which node is which; with them, reuse becomes something a program can control deliberately.
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.
- Hazelnut: a bidirectionally typed structure editor calculus POPL 2017 · pp. 86–99 · doi:10.1145/3009837.3009900 Every edit state has a static meaning, so an incomplete program is still a program.
- Live functional programming with typed holes POPL 2019 · PACMPL 3(POPL), 14:1–14:32 · doi:10.1145/3290327 And a dynamic meaning too, so an incomplete program still runs. This is what makes a livelit's expansion evaluable while the program around it is still being written.
- Filling typed holes with live GUIs PLDI 2021 · pp. 511–525 · doi:10.1145/3453483.3454059 Livelits: the graphical literals the first Fumola integration was built out of.
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.
- Fungi: Typed incremental computation with names arXiv:1808.07826 · v1, 20 August 2018 · the latest version A type-and-effect system in which the programmer states local invariants about names and the system composes them into a global uniqueness property, with a bidirectional formulation and a prototype in Rust.
- Refinement types for precisely named cache locations arXiv:1610.00097 · v5, 21 October 2017 · v1 was titled “Typed Adapton” The predecessor: refinement types ensuring each name identifies at most one value or subcomputation.
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.
Where to go next#
- /press/ Press What has been written about Fumola from outside this repository, newest first.
- /alignment/ Alignment What it means for the graph to agree with itself, how an edit breaks that, and how signaling and repair put it back. With the Recipe's rules.
- /web-play/ Web play Write Fumola in the browser with the library beside you. Nothing to install.
-
Adapton/fumola
The language
The Rust implementation, the Fumola module library under
fumola/, and the examples. - adapton-recipe The Extended Adapton Recipe The semantics Fumola implements: DCGs with symbolic space and time.
- replayground mergeSort on 44 elements An interactive DCG, exported from a Fumola program that read its own history.
- hazel branch The Hazel integration, running Both Fumola routes in one live build, and Blackboard beside them. Open the decks from the documentation slides.
- docs/experimental-language-integrations.md The design document for that branch — what three sub-languages in one tile grammar cost, and the two Fumola routes compared claim by claim.
- docs/fumola-tiles-design.md The tiles route: what you author instead of a parser, and why the obligation moves from parsing to printing.
- src/language/fumola The Hazel side of the tiles route — the grammar, the printer, the run path and the adapton history the panel reads.
- The tiles deck Ten slides: the form itself, instance and mode, a level tree, Hazel inside Fumola, the round trip, self-inspection. Source for the slides in the live build.
- Notebook semantics, and programmable probes Two design notes on what the integration turned out to be for: what actually runs when you edit, and Fumola instances as probes that survive the edit. Proposals, not results.
- docs/livelits.md The first route: what the four livelits are, what crosses the boundary, and what is not settled.
- The livelit chapters A tour of Fumola as Hazel first saw it, in nine slides of increasing complexity — values, symbols, cells, thunks, input, runtimes, then two worked examples from this repo.
- crates/fumola_wasm The JS boundary The instance store, the value translation, and the API this host publishes.
-
adapton/
The two semantics
simple.rsandgraphical/, behind oneAdaptonStatetrait. - system/adapton.fumola The graph, in Fumola The value-level types for the store, and the outline and scene exports built on them.
- dfinity/motoko.rs Where the surface came from The Motoko interpreter in Rust that Fumola descends from.
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.
| dirtying | signaling — the traversal an edit starts, from the changed cell toward its readers |
| dirty | signaled — an edge whose recorded observation may no longer agree with the graph |
| clean | aligned — an edge that does agree, or a whole node all of whose edges do |
| cleaning | repair — the traversal a force runs on a thunk with a signaled edge: check each edge, re-evaluate if one cannot be realigned |
| change propagation | realignment — 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.
- /alignment/ Alignment The prose and the formal view: how the graph realigns after signaling, via repair.
- docs/realignment.md The effort, the words, and what is left Why repair was built now, what the numbers said, and the to-do lists — engine, tools, measurement, Recipe, docs.
- #106 Signaling and repair: the graphical cache realigns after an edit The change itself. Its module documentation is a rule-by-rule reading guide to the papers and the Recipe.
- #22 Repairing the graphical cache: signaling and realignment algorithms The issue that has tracked this since March; the two unchecked boxes were signaling and repair.
- adapton-recipe The Extended Adapton Recipe The formalism this runtime implements. Being brought into line with the code, which is the reference.