Learn · from docs/semantics-v0.1.md in the eLucid8 repository

Contents

eLucid8 semantics sketch (v0.1)

This note proposes the smallest semantic core worth prototyping. It is a design draft, not a settled language specification.

Provenance labels. Each section is tagged with where its rules come from, following cos/LUCID-BACKGROUND.md §5:

Rules marked "owner decision" have been decided. Anything else tagged [eLucid8] or [Runtime] remains a proposal.

1. Context-indexed values

Provenance: the context-indexed value model is [Lucid] (§9: L1, L2). Typed coordinates and the outcome set are [eLucid8].

A value is a partial function from a logical context to a value:

Value : Context -> T
Context = DimensionName -> Coordinate

Each dimension has a declared coordinate domain and identity. Examples include integer step, event-time time, integer x/y, and categorical model_version. A context contains coordinates only for the dimensions relevant to the value.

(Owner decision, 2026-10-02.) Coordinates are typed. In v0.1 a coordinate is either an integer or a text label, and equality compares type and value: integer 1 and text label "1" are different coordinates. Decimal (floating-point) coordinates are not supported in v0.1. Event times are integers in a declared unit.

Evaluating a value at a context has exactly one outcome:

Outcome<T> = Value(T) | Undefined | Error(E)

Undefined means the context is outside the value's domain (the value is a partial function). Error means evaluation failed. The two are always distinct.

A stream is simply a value whose context has one dimension:

x@{time = t}

A multidimensional value has several coordinates:

y@{time = t, x = i, y = j}

Dimensions are logical axes. They do not inherently mean wall-clock time, physical location, tensor layout, or device placement; those meanings are declared or mapped separately.

2. Equations and demand

Provenance: demand-driven evaluation is [Lucid] (§9: L3). Rejecting unguarded cycles, projection, propagation, boundary policies and snapshots are [eLucid8]. §9 notes where these agree with Lucid and where they differ.

An equation defines how to evaluate a named value at a context. Evaluating a requested point recursively requests the points named by its dependencies. Only demanded values need to be evaluated.

z@{time = t, x = i} = f(a@{time = t, x = i}, b@{time = t, x = i})

The evaluator records dependencies for each computed point. A demand trace should identify the requested coordinate, evaluated dependencies, cache hits, and any effects encountered.

Recursive definitions require a well-founded demand rule or an explicit recurrence operator. The initial prototype should reject unguarded cycles rather than guessing an evaluation order. (This departs from Lucid, where an unguarded definition such as X = X + 1 denotes ⊥ and a demand-driven interpreter loops forever; §9: L4.)

The following rules are owner decisions (2026-10-02).

Projection. When a value is referenced in a context with more dimensions than it declares, the context is projected to the referenced value's dimensions. For example, bias@(time) used at (time, x, y) is looked up at time alone and does not depend on x or y. If a referenced value needs a dimension that the context does not supply, for example alarm@(time) using field@(time, x, y), the program is rejected before evaluation unless the missing coordinates are given explicitly.

Propagation. If an outcome depends on a dependency whose outcome is Error, it is an Error that identifies the upstream error. It is never silently replaced by an ordinary value: if score is late or fails, alarm is an upstream error, not false. A dependency outcome of Undefined propagates as Undefined. If the dependencies include both Error and Undefined, the result is Error. Strictness (owner decision, 2026-10-02; supersedes the earlier "every operator is strict"):

Lists are not comparable (owner decision, 2026-10-04). [eLucid8] A list is what a neighbourhood or a window alignment gives. == and != with a list operand (on either side) give an Error ("== needs values, not lists"). Strictness is unchanged: both operands are demanded together, so an Error operand wins, then an Undefined one, before the list is examined. (Before this decision the reference compared lists by identity, so the result could depend on whether memory management had recomputed a list.)

Non-finite numbers (owner decision T4, 2026-10-03). [eLucid8] If an arithmetic operator or an aggregate produces a non-finite number (Infinity, -Infinity or NaN), the outcome is an Error ("non-finite"), not a value, consistent with division by zero. It propagates like any other Error, so the trace names where a computation first overflowed. External source data is not affected by this rule; a source defined by an eLucid8 formula (as in the playground) is an expression, so the rule applies to it (owner, 2026-10-03). Likewise, a service's answer is external: a non-finite number a service answers is a value, like source data, until an operator produces a non-finite result from it (owner decision, 2026-10-04; §5).

Explicit error recovery may be considered later.

Neighborhood boundaries. A neighborhood operation must declare a boundary policy explicitly. The spatial demo uses truncation: only neighbors inside the declared coordinate domain are included, and the aggregate (for example, an average) is computed over the neighbors that are available. Truncation drops only out-of-bounds neighbors. An in-bounds neighbor that is Undefined or Error propagates as usual.

Non-numbers in neighbourhood aggregates (owner decision, 2026-10-04; closes Q9). [eLucid8] sum, mean, min and max of a list (a neighbourhood or a window alignment) whose elements include a non-number (text, a boolean or a list) give an Error ("sum needs numbers"), as for aggregates over declared ranges and as + does; count counts any values. An Error or Undefined neighbour still decides the outcome first, as above. (Before this decision the reference converted non-numbers as JavaScript does.)

Maths functions (owner decision N1, 2026-10-03; proposals/milestone-5-network-v0.1.md). [eLucid8] exp(e), log(e) (natural logarithm), sqrt(e), tanh(e), abs(e), and the two-argument min(a, b) and max(a, b).

Aggregates over declared ranges (owner decision A1–A4, 2026-10-03; proposals/aggregates-v0.1.md). [eLucid8], after MDP's sum_z(product_z) (§5.3, p. 91) [Lucid].

Stream operators (owner decision, 2026-10-02; proposals/streams-v0.1.md). Along an integer dimension d with origin o (the range minimum, else 0):

These follow Wadge & Ashcroft (pp. 47, 61) [Lucid]; the dimension suffix and the rule before the origin are [eLucid8]. fby demands only the operand it selects. Recursion that is not well founded (for example x = next.t x) stops at a demand-depth limit on logical depth (default 1,000,000) and is reported as a ProgramError, never as a value. (Owner decision, 2026-10-04.) [Runtime] The depth limit is a resource limit, like memory: whether a query stops with the depth-limit ProgramError may depend on runtime choices (evaluating a recurrence by levels or per element, retention, suspension), so a deep, well-founded history may reach the limit under one runtime policy and not under another. Outcomes (Value, Undefined, Error) never depend on runtime choices.

Functions, where and local dimensions (owner decision, 2026-10-02; proposals/functions-v0.1.md).

Source snapshots. Each query pins an immutable version of every source it reads, and no source may advance during evaluation. Together these version pins form the query's snapshot.

Retaining source versions. (Owner decision, 2026-10-02, following eduction's usage counts; §9: L12. MDP p. 100 notes that recomputing a discarded value assumes "the input values to the program are available", and this rule guarantees that for pinned versions.) Each source version has a usage count: the number of running queries that pin it plus the number of usable cache entries that read it. While the count is above zero, the version must stay readable. When it reaches zero, the version may be removed. If a demand needs a version that has been removed, the outcome is an Error (source version unavailable), never a value computed from a different version.

3. Logical time and physical clocks

Provenance: logical time not counting a global clock is [Lucid] (§9: L5). Clock mappings, alignment policies and deadline outcomes are [eLucid8].

Logical coordinates are not durations. A logical step = 12 identifies a position in a history; by itself it does not assert that twelve milliseconds or twelve seconds have elapsed.

A clock mapping associates logical ticks with physical events or deadlines:

clock sensor_clock:
  source = sensor.timestamps
  deadline = 20ms

Clocked dimensions can have different rates or be event-driven. Combining values from distinct clocks requires an explicit alignment policy (for example, latest-at-or-before, exact timestamp, window, or hold-last-value). There is no implicit global order across independent clocks.

Clocks and alignment (owner decision, 2026-10-02; rules C1–C6 in proposals/clocks-alignment.md). [eLucid8]

Lustre's current (holding the last value of a slower clock) is a related precedent for hold. Lustre clocks, however, derive from one base clock, whereas eLucid8 clocks are independent and aligned by timestamp. Wadge & Ashcroft's hiatons (p. 111) are not adopted.

The runtime records actual start and completion times. A deadline miss is an observable result, not a silent semantic substitution. Hard real-time guarantees require bounded execution and bounded-latency storage; a prototype can report timing but should not claim hard real-time certification.

4. Point and cluster storage

Provenance: [Runtime], except that typed key equality and the validity of cache entries with respect to snapshots follow from [eLucid8] rules in §1–§2. Caching values by variable and context has a precedent in Lucid eduction implementations (the "warehouse"; §9: L6). That precedent concerns implementations, not language semantics. Cluster entries are new in eLucid8: Lucid implementations stored and computed only individual values, never tiles or blocks (§9: L11). GLU's "granularity" concerned coarse-grained computation nodes, not blocks of stored values (§9: L8).

The semantic object is the value at a logical context. Storage granularity is an implementation decision:

For example, a cluster may cover a fixed time and an x/y tile. A point demand can be satisfied from a containing cluster. A cluster request can be assembled from point entries if its coverage is complete.

The initial prototype should require exact, typed coordinate keys and explicit cluster coverage. Hashing may index entries, but a hash collision must never imply coordinate equality. Cluster overlap, precedence, and invalidation rules must be deterministic.

Cached pure values are reusable only while their dependencies and definition versions remain valid. Memory limits and eviction may affect performance but must not change results.

The cache rules below were accepted by the owner on 2026-10-02. Worked examples are in proposals/point-cluster-rules.md.

5. Effects and AI operations

Provenance: [eLucid8] for the observable rules: outcomes, propagation, no speculation and memoization within a query. [Runtime] for reuse across queries, the retry mechanism and adapters. No Lucid precedent for calls to external services (such as model calls) or effect caching was found in the sources checked so far.

Pure equations are deterministic within a program version. An effect calls an external service: a program outside eLucid8 that answers on request, such as an AI model or a web API (after first use, "service"). A source is external data you read; an effect calls an external service that computes an answer. (Naming: owner decision, 2026-10-04; the rules below are unchanged.) External operations are explicit effects and return structured outcomes:

Result<T, E> = Ok(T) | Error(E)

A service call records its input identity, the service's identifier and version (for an AI model: the model identifier/version and the prompt or operation version), request options, start/completion time, and outcome. Its cache and retry policy must be specified. Replaying a recorded response is distinct from making a fresh request to the service.

The first adapter should expose service calls as coarse operations over ordinary values or clusters. Mixing simple operators with coarse-grained nodes that run chunks of conventional code has a precedent in GLU (§9: L8). Token-by-token streaming is outside the first milestone.

The following rules are owner decisions (2026-10-02, R4).

6. Logical axes and physical placement

Provenance: [eLucid8] proposal.

Logical dimensions can describe batch, sequence, layer, tensor coordinates, or space. A separate placement plan maps regions or clusters to devices and specifies any required communication. Placement must preserve the logical value semantics.

The first prototype will not compile training graphs or choose optimal sharding. It should provide a trace format that could later record logical coordinates, storage clusters, physical device, communication, and timing.

7. Prototype acceptance questions

The prototype should answer these design questions with executable examples:

  1. Can one-dimensional streams and multidimensional values share the same context model?
  2. Can a point demand evaluate only the dependency points it needs?
  3. Can point and cluster caching return equivalent results under overlapping demands?
  4. Can independent clock domains be combined only through visible alignment rules?
  5. Can a late or failed external service call be represented without pretending it produced an ordinary value?
  6. Does the demand trace make it clear why a value was computed, reused, or marked late?

8. Small demonstration workload

Use a finite time-varying spatial field. The source provides sensor samples at (time, x, y). A pure neighborhood operator with a truncation boundary policy gathers local values; an effectful model adapter (deterministic in the first prototype) scores a region; a deterministic rule emits alarms. Query a few alarm coordinates and compare point caching with tile caching. Include timestamps and a simulated deadline so clock semantics can be explored without requiring real-time hardware.

This workload exercises multidimensional contexts, demand, clusters, effects, and real-time mapping while remaining independent of distributed training infrastructure.

9. Lucid lineage and sources

The following was checked on 2026-10-02 against the primary sources listed in references.md. W&A is Wadge and Ashcroft, Lucid, the Dataflow Programming Language (1985); page numbers are the book's own. Wadge 2022 is "We Demand Data — the story of Lucid and Eduction". Ashcroft, Faustini, Jagannathan and Wadge, Multidimensional Programming, (cited as MDP) has been checked for §5.3 (pp. 89–91), §6.2.4–6.3 (pp. 110–112) and §8.4 (pp. 146–148), from OCR excerpts supplied by the owner, a co-author. Quotations come from those excerpts; where the OCR is garbled, the text is paraphrased rather than quoted. The sections most relevant to eLucid8 are §2.2 (the intensional language Lucid), §2.4 (space and time), §3.2 (denotational semantics), ch. 5 (eduction, especially §5.3–5.4), §6.2.4 (fault-tolerant eductive evaluation) and §8.4 (multiple dimensions).

10. Open questions