Skip to content

Speculative Foreign Names

enumeratio edited this page Oct 1, 2026 · 1 revision

Foreign names: shadowing at every format boundary

Speculative design. Open, not settled — thinking and proposals, not shipped behavior. See Roadmap for what's in flight.

Status: speculative (2026-10-01). Every system we read from or write to has globals: names that already mean something there. A variable that crosses into a system where its name is a global gets captured, and silently means something else. This page is about finding those globals per system and renaming around them at the boundary, once, in the format, rather than in every symbol.

1. Two directions, and the answer coming back

Inbound (a foreign expression read as ours). A foreign variable whose name is one of our globals is captured by our definition. The case we've hit: Wolfram's documentation uses e and i as plain variables (a list element, a loop index), and compute-engine reads the MathJSON symbols e and i as Euler's number and the imaginary unit wherever it boxes them. About seventy farmed rows read {a, b, c, d, e} as a list ending in Euler's number. The Wolfram adapter now renames a variable e or i to a spare letter (#518, applied to the farmed rows in #541).

Outbound (our expression written for another system). One of our free variables whose name is a global there is captured by that system:

  • Wolfram. Emit passes a free name through by name. A free N, D, C, E, I, O or K of ours would be Wolfram's function or constant.
  • SymPy and Sage. They don't auto-declare names, so emit already spells a free variable explicitly (Symbol("x"), SR.var("x")), and capture can't happen there. Bound names still can: a Sum index, a Function parameter, a loop variable in a harness. One scan has already collided a harness loop variable with the i inside i_1 (see Algebras, Units and Places §5).
  • Code lanes (Python with mpmath, Rust, Julia, WGSL, JavaScript). They emit bare identifiers, so a language's builtins and keywords (sum, max, len, lambda), and the globals the lane imports (from mpmath import * brings in e, pi, j, inf, eps and more), are all candidates. Most aren't audited.

Not every system binds e. The point is that each binds something, and Sage binds a great deal. So the work per system is to know its globals, not to assume they look like ours.

The answer coming back. An answer that mentions a renamed variable is renamed back before it's compared or shown. So a rename is a map that goes out with the expression and inverts on the answer, never a one-way edit.

2. Not each symbol's job

A symbol package contributes how its heads are spelled in a format (registerFormat rules, or a row in a fixed syntax's table: Kernels and Front Ends §6, Formats). It shouldn't have to know that N is taken in Wolfram, or lambda in Python. The format package owns its language's globals and the rename pass around them. Every symbol's spelling, including one from an external registry (Lazy Engine §3), goes through that pass without knowing it's there.

So a format package gains:

  1. A reserved set: the language's globals, generated where the language can list them:

    • Wolfram: Names["System*"](we already keep this,system-names.ts`);
    • Python: keyword.kwlist, dir(builtins), and the lane's namespace after its imports;
    • Sage: globals() at startup;
    • Julia: names(Base) plus the lane's usings;
    • Lean: what the opened namespaces export;
    • ours: the names compute-engine has a definition for (DEFINED_NAMES, from lookupDefinition).

    A generated set is committed with a drift check, as the system names are now.

  2. A rename pass, on the way out and on the way in: any free or bound name that collides with the other side's reserved set is renamed to a fresh one, which both sides leave free and the expression doesn't already use. The choice is deterministic (a fixed spare-letter order, as the Wolfram adapter's is), so ids, goldens and captions stay stable.

  3. The inverse map, applied to whatever comes back.

The free-variable rule the oracles already use (a name is free exactly when compute-engine has no definition for it) is the inbound reserved set's other face: one table answers both "is this ours?" and "would this be captured here?".

3. Today's pieces, and where they go

piece today moves to
Wolfram's globals wolfram/src/system-names.ts the Wolfram format package's reserved set
inbound rename, Wolfram renameConstants in reference/scripts/wolfram-examples.ts, e and i only the Wolfram format's inbound pass, over our whole reserved set
outbound, SymPy/Sage oracle/src/emit.ts wraps free symbols explicitly stays, plus the pass for bound names
outbound, Wolfram passes free names through the pass, against system-names
code lanes bare identifiers each lane's format: language keywords, builtins, imports
our globals oracle/src/defined-names-data.ts shared by every format's inbound pass

4. Epsil's own e and i

Separate from this, and already raised with compute-engine: Epsil itself reads a bare e or i as a constant, so a hand-written example can't use either as a variable. Until that's settled upstream, hand-written rows rename (e and i to k in At, Do, Module), and our reserved set includes both. When Epsil changes, only the set changes.

5. Testing

Per format, a generated round trip: an expression whose free and bound variables are exactly the other side's reserved names. Out, evaluated there, back: the result must name the original variables and leave them free. It's cheap, needs no examples, and an external package that arrives with a new spelling gets checked by it for free.

Open

  • How far to generate. globals() in Sage is large and includes anything a lane imports. A set that's too wide renames needlessly; one that's too narrow misses captures. Possibly: the generated set, intersected with the names our expressions actually use (as DEFINED_NAMES is scoped to the reference data).
  • Names inside strings and options. An option key or a string argument isn't a variable; the pass must not rename it.
  • Captions and prose that mention a renamed letter.
  • Wolfram's capitals. Wolfram's own documentation never uses E/I/N as variables, but other sources (Fungrim, a user notebook) might.

Home

Project

Design: every design page, settled, speculative and archived

Clone this wiki locally