Module signal

Module signal 

Source
Expand description

The signal graph, as a graph.

docs/03-type-and-effect-system.md §3.7: “The signal graph is a graph, not a pipeline. This section reads top-to-bottom, and the programs it describes do not: events is decided from the state, and the state is folded from events. The cycle is real and it is sound.”

Phase 1 and Phase 2 read the graph by recognising one shape: find the merge_clients(), find the durable, find the decide, find the first client-placed signal, and inline everything between them (docs/19-phase-1-report.md §19.9). That was legitimate narrowness because it announced itself — nine diagnostics refused every other shape — and it was named as debt by two phases running. It also had a hole neither report knew about: a program with two durable folds matched the shape, was accepted, and was sliced with both folds reading the same accumulator. See docs/23-general-slicer-report.md §23.2.

This module is the replacement. It does not recognise a shape. It builds the graph the program wrote — one vertex per signal operation, including the ones nested inside a declaration — computes its strongly connected components, and hands crate::split a structure to slice. What used to be “the durable one” is now “the vertices whose op is Op::Durable”, and there may be any number of them.

§What a vertex is

A declared signal contributes one vertex per prim application in its expression, not one per declaration. todos: Signal[State] = durable(fold(apply_event, empty, events)) is two vertices — a Op::Durable over a Op::Fold — because the fold is a node in the dataflow whether or not the program gave it a name. Only the outermost carries the declared name; the inner one is labelled todos·fold so a diagnostic and beck explain flow can still point at it.

That is the difference between a graph and a pattern: map2(f, durable(fold(…)), summary) needs no new case here, because there was never a case to begin with.

§Cycles

The condensation is computed by crate::graph::DepGraph, which already does Tarjan iteratively over a CSR adjacency and numbers components in topological order. Reusing it rather than writing a second SCC pass is the point of it being a separate module.

One rule is imposed on the result: every cycle must contain a fold. The decide → durable → fold → decide cycle is sound because the fold is where the recursion bottoms out — the accumulator is a value the slicer can take as a parameter. A cycle of pure signal_maps has no such point and is a program with no meaning; Graph::build refuses it by name rather than looping.

Structs§

Cut
A dataflow edge whose two ends are on different tiers.
Graph
The signal graph of one program.
Node

Enums§

Op
What a vertex does. One variant per construct in §3.7’s signal vocabulary.

Constants§

FUSED_STATE
The accumulator a program with several durable folds is compiled to.

Functions§

durables
Every durable a set of signal declarations holds, labelled exactly as Graph::build labels it, in declaration order.
fused_state_decl
The synthetic accumulator a program with several durable folds is compiled to.
signal_elem
The element a Signal[T] or Stream[T] carries.

Type Aliases§

SigId
An index into Graph::nodes.