Volt#

Volt is the layer above Ohm, and the thing it is built out of is a graph.

sites  = [apple, nvidia]
topics = [earnings, layoffs]
jobs   = sites × topics

found = search(jobs, k: 3)
brief = summarize(found, words: 60)

table(brief)

No model sits between what you write and what runs. The text is parsed by a parser, turned into a directed graph of what depends on what, and the graph is evaluated. The same program always does the same thing.

Open any file in volt/ in the Scripts panel and press Run. npm run volt -- file.volt prints the graph without running it, and volt.html in the repo root is the same material as a single page, generated from the operator registry and the example files.

Why a graph#

Once a program is a graph rather than a sequence of statements, four things come for free.

Independent work runs at once. Nodes that do not depend on each other are evaluated together — globally, across the whole program, not one call at a time. Four searches over four jobs are four concurrent searches.

Only what changed is re-done. Each node is keyed by what it is and what it was given. Edit one topic in a program with twelve searches and the eleven unaffected branches keep their values; the console tells you how many it reused.

A program can be priced before it runs. The model calls and page loads are countable without executing anything. The console prints the estimate first.

Building it touches nothing. Parsing and graph-building are pure — no network, no model, no files. It is safe to do on every keystroke, and on a program you have not read.

The whole language#

program   := (statement NEWLINE*)*
statement := names '=' expr | expr
names     := NAME (',' NAME)*            also  name '[' args ']'
expr      := pipeline
pipeline  := cross ('|' call)*
cross     := primary ('×' primary)*
primary   := NAME+ | NUMBER | STRING | list | call | '(' expr ')'
list      := '[' (expr (',' expr)*)? ']'
call      := NAME '(' (arg (',' arg)*)? ')'
arg       := (NAME ':')? expr

That is all of it. Two pieces of surface syntax are resolved in the parser, so the tree stays this small: x | f is exactly f(x), and f[a] = x is exactly f(x, a).

Alternative spellings, because people type what they type: × can be x or *, | can be |> or ->, and a comment is # or --.

Values#

A bare word is a reference to a binding if one exists, and otherwise the word itself. A run of bare words is one phrase, so [latest earnings, layoffs] is two items, not three.

sites = [apple, nvidia]     # two strings
best  = sites               # a reference to the list above

Quote anything containing punctuation: "research/summary" would otherwise stop at the slash.

Every pair#

× between two lists is the Cartesian product.

jobs = sites × topics       # 2 sites by 2 topics is 4 jobs

A lone x between two things means the same; xs is just a name.

Verbs#

The vocabulary is Ohm's operators, lowercased — all 49 of them. Volt invents no semantics; it is a different way to write the same operators with the dependencies made explicit.

brief = summarize(found, words: 60)
best  = rank(papers, query: "sample size")

Named arguments are the operator's configuration and must be plain values. A handful of everyday spellings are accepted too: find, compare, keep, summarise, tabularize, open.

Several verbs on one line apply in order, each naming its own result:

summarize, join = notes     # join sees what summarize produced

Map or fold, decided for you#

An operator that takes one item, handed a list, runs over every item. An operator that takes the whole list folds it.

summarize(notes)   # three notes, three model calls, three summaries
join(notes)        # three notes, one model call that sees all three

You never say which. It is read off each operator's own signature, so there is no second table to keep in step — and the classification is exhaustive, so an operator added with an argument name the rule has never seen fails a test rather than being quietly mapped.

Output#

print, table, display[...] and present are the words that produce output rather than a value. present renders a view template into the Results panel:

present(brief, template: "research/summary", title: "Four day week")

Reading the graph#

npm run volt -- packages/volt/examples/02-research.volt:

            · "apple"
            · "nvidia"
  sites     [] list("apple", "nvidia")
  topics    [] list("latest earnings", "layoffs")
  jobs      ×  cross(sites, topics)
  found     ∀  Search(jobs; k=3)
  brief     ∀  Summarize(found; words=60)
            →  table(brief)        ⟵ output

  10 nodes · at least 1 model call(s) · at least 1 page load(s)
Symbol Node
· a constant
[] a list
× every pair
ƒ an operator over the whole collection
∀ an operator over each item
→ output

∀ Search is the map decision made visible: Search takes one query and was handed four jobs, so the graph records a map node and the four searches run together.

Running one#

The console narrates it:

volt: 10 nodes, 10 to compute, at least 1 model call(s)
  sites = a list of 2
  jobs = every pair
  found = Search over each
volt: 10 computed, 0 reused, 2140 ms

Run it again without changing anything and every node is reused. Change one line and only that branch recomputes — the counts at the end say which.

The session holding those values lives as long as the script stays open. Closing it, or changing the model profile, starts again from nothing.

When to use Volt, Ohm or Bolt#

Ohm You want control flow, variables, arbitrary Lua, and exact control. Everything Volt can do, Ohm can do, more verbosely.
Volt The work is a pipeline over lists, and you want it parallel, re-runnable and inspectable without writing any of that yourself.
Bolt You are not sure what you want yet. Describe it in English, read the Volt it produced, keep it.

Volt's verbs are Ohm's operators and its evaluator calls the same prelude, so nothing is reimplemented and nothing is lost by moving between them.

What is not built yet#

The visual editor over the same graph. The graph is the right shape for one — the evaluator already reports each node as it starts, finishes, or is reused — but the canvas is not written. Until then the graph is text, from npm run volt or from the console.