Or, what if you could dial how much English lives in your code, per line?
Modern LLM code generation has a strange property. Your program's behavior is a function of your prompt, the model's mood, and whatever context happened to be in memory. There's no single artifact you can point to and say "this is what runs." Same input, different output. Same request, different code.
You can work around this — cache LLM output, freeze model versions, forbid regeneration at runtime. But you're always working around the LLM, never with it.
E-- flips the shape. What if the LLM's involvement was constrained to a specific structural slot in your source, resolved once at compile time, and everything else was normal deterministic code? Now you can pick, per value, how much LLM you want in your program. Once picked, the result is byte-for-byte reproducible.
You can dial the English-Python ratio per line, per value, per region. Whichever end of the spectrum makes sense right there.
The dial
Here's the same intent expressed at three different points on the dial.
Fully coded — you commit to every value:
Set tempo to 70.
Set piece to Call [[compose_blues]] with tempo=tempo, key="E".
Return piece.
Mixed — structure is code, one value is English:
Set tempo to {{ a tempo appropriate for a slow blues }}.
Set piece to Call [[compose_blues]] with tempo=tempo, key="E".
Return piece.
More English — most of the intent is described, not written:
Set piece to {{ compose a slow blues in E with a tempo appropriate for the style }}.
Return piece.
Same intent. Different English-Python ratios. Author chose.
The {{ ... }} markers are slots — placeholders where you write English instead of a value. E-- treats them as first-class syntax. At compile time, the compiler resolves each slot to a concrete Python expression. Then normal deterministic code runs.
What E-- looks like
E-- has a closed grammar of English-ish verbs: Define, Set, Let, Do, Call, Return, Give back, For each, If, Otherwise. Statements end in a period. Function references appear as [[wikilinks]].
A canonical program:
Define [[describe]] taking n:
If n is greater than 10:
Give back "big".
Give back "small".
For each n in <3, 42, 7>:
Do [[print]]([[describe]](n)).
Compiles to:
def describe(n):
if n > 10:
return "big"
return "small"
for n in [3, 42, 7]:
print(describe(n))
Verb-first, sentence-shaped, but structured enough to parse deterministically. Reads out loud like English.
Slots — where English lives inside code
A slot is a {{ ... }} placeholder containing an English description. At compile time, the compiler dispatches an LLM to resolve the slot to a concrete Python expression.
Value slots — the simplest case:
Set tempo to {{ a tempo appropriate for a slow blues }}.
The compiler asks the LLM: "convert 'a tempo appropriate for a slow blues' to a Python expression." LLM returns 70. Compiler splices it in. Final code: tempo = 70.
Code slots (v0.2.0) — for whole regions:
Set tempo to 70.
{{ create a harmony using form with the tempo }}
Set piece to Call [[voices_list]] with sections=[harmony].
Return piece.
The slot at line 2 is at a statement position — the compiler asks the LLM to produce statements (plural), which get spliced in at proper indentation.
Resolution is a one-time cost. The compiler caches every slot resolution keyed by the slot's text. Subsequent builds are cache hits — no LLM call, byte-identical output. Once your slots are resolved, the resulting Python is a normal deterministic artifact you can inspect, run, share, freeze, whatever.
Why this matters
Accessibility meets precision. Programming languages have historically had to pick. English-like DSLs (Inform 7, AppleScript, SQL) traded expressiveness for readability. Full programming languages trade readability for expressiveness. E-- doesn't force the choice: use precise syntax where you know what you want; use English where you'd rather describe. Both on the same line if you want.
Domain experts can code the parts they know. A musician writes structural composition — call these functions, in this order, combine them. They defer specifics — key, tempo, scale characteristics — to English descriptions. Same for a biologist authoring a simulation: fix the experimental structure, let the LLM pick reasonable parameters. Same for a designer, a hobbyist, a student.
Runtime is deterministic and cheap. Because slot resolution happens at compile time and gets cached, no LLM call ever happens at runtime. Same source, same behavior forever. Cost scales with authoring, not execution.
Programs are inspectable at multiple levels. You have the source (structured, human-readable, English-y), the resolved intermediate (still E--, slots now filled), and the compiled Python. Three artifacts you can look at when things break. Traditional LLM-generated code gives you only the natural-language input and the (possibly regenerated) output — the intermediate structure has evaporated. Debug that: you've got a bug you can't reproduce, in code you didn't write, on a system that costs you an API call to inspect.
Static analysis is easy on structured code. Chip palette insertion, dependency graph extraction, wikilink navigation — all straightforward on E-- syntax. Trade-off: inside a code slot, the resolved code is opaque to structural tooling. You choose per-slot whether structure or delegation matters more.
An example consumer: Forge
E-- exists because a project called Forge needed a language its cohort could author.
Forge is an environment for creative work — music composition, generative art, running simulations — where non-programmers author computable notes. The cohort's users are musicians, artists, researchers. Python was inaccessible. Description-only was imprecise. E-- filled the middle.
Every note has three facets: a Description in prose, a Recipe in E--, and the compiled Python. All three are visible in the editor at once.
Here's a Recipe for a 12-bar blues:
Let harmony = Call [[form]].
Let drums = Call [[drum_chorus]] with profile="standard".
Let guitar = Call [[guitar_solo_chorus]].
Let piece = Call [[voices_list]] with sections=[harmony, drums, guitar].
Return piece.
form, drum_chorus, guitar_solo_chorus, voices_list are library functions in Forge's music library. The Recipe composes them like building blocks.
That Recipe transpiles to Python which constructs the musical score (via music21) and renders it as MusicXML in the user's editor. Once the Recipe is written, everything downstream is deterministic. Two users open the same note and get the same blues.
A more advanced user might write a Recipe with slots:
Let tempo = {{ a tempo appropriate for a slow blues }}.
Let key = {{ a key that pairs well with a moody vocal style }}.
Let piece = Call [[compose_blues]] with tempo=tempo, key=key.
Return piece.
They know the structure — compose a blues, at some tempo, in some key. Specifics they'd rather delegate. LLM resolves once at compile time; result caches; subsequent renders are pure.
Forge is one consumer. E-- itself is a language substrate any system could build on. If you want your users to author programs that are precise enough to run but accessible enough to read — with escape hatches to English wherever they want them — E-- is a shape that works.
Where to go next
Try it in 30 seconds:
pip install e-minus-minus
Then a tiny E-- program:
printf 'Set x to 5.\nDo [[print]](x).\n' > hello.emm
emm-transpile hello.emm
Prints:
x = 5
print(x)
That's it. No LLM required for canonical E-- with no slots. See the README for slot resolution and free-English input.
E-- itself:
-
E-- on PyPI —
pip install e-minus-minus - E-- on GitHub — source, examples, and the CLI
- The E-- language specification — the full grammar, the semantics, the design decisions
- The E-- README — quick-start, install, worked examples
Forge (an example consumer):
- Forge on GitHub — the Obsidian plugin that turns E-- Recipes into rendered music, generative art, and simulations
Get in touch:
If you're building a tool that could use E--, if you have questions about the design, or if you'd like to collaborate on where the language goes next — email me at frmoded@gmail.com. E-- is small and open-source (Apache 2.0). If you use it in your own project, let me know what you build.
Top comments (0)