Add tuple-output functions#938
Conversation
Functions may now declare more than one output sort, e.g. `(function interval (Math) (i64 i64) :merge (values (max old0 new0) (min old1 new1)))`. Outputs are stored as separate value columns (no boxing); the functional dependency maps the keys to a tuple of values. They are destructured in queries with `(= (values lo hi) (f x))`, written with `(set (f x) (values lo hi))`, and merged with a `(values ...)` clause whose i-th element merges column i using the bound variables `old0`, `new0`, `old1`, `new1`, .... - egglog-bridge: generalize `SchemaMath` to independent key/value column counts and add a per-column `MergeFn::Columns` (with `OldCol`/`NewCol` leaves). The underlying core-relations table already supports multiple value columns. - front-end: `Schema`/`FuncType`/`ResolvedSchema` carry extra output sorts; new `ResolvedCall::Values` plus `values` lowering in facts and `set` actions. - tuple outputs are restricted to plain functions (not constructors, relations, or view tables) and are unsupported by the term/proof encoding. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #938 +/- ##
==========================================
+ Coverage 86.65% 86.72% +0.07%
==========================================
Files 90 90
Lines 27057 27483 +426
==========================================
+ Hits 23445 23836 +391
- Misses 3612 3647 +35 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Merging this PR will improve performance by 5.18%
|
| Mode | Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|---|
| ⚡ | Simulation | tests[repro-665-set-union] |
629.1 ms | 598.1 ms | +5.18% |
Tip
Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.
Comparing oflatt-claude:tuple-outputs (d7a1ba2) with main (42a4fb6)
Footnotes
-
226 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩
0ad1048 to
3197ad2
Compare
`values` is the tuple constructor for tuple-output functions and appears in expression-head position, so it must not be usable as a user identifier. While here, also reserve the other built-in keywords — most command, action, and schedule heads (`function`, `set`, `union`, `rule`, `run`, ...) — so a function named e.g. `set` can no longer be confused with the `set` action. Names starting with `:` are reserved too, since the parser uses that prefix to mark option keywords (`:merge`, `:cost`, ...). `input` and `output` are common words, so they are only *partially* reserved: usable as variables, but not as definition (table/sort/etc.) names nor as the head of a call expression (so `(input ...)` always means the command). egglog previously only reserved an internal symbol *prefix*; this adds an exact-name keyword list (and the `:` prefix), checked when parsing declared names and variable references, and skipped while re-parsing egglog's own generated programs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3197ad2 to
d7a1ba2
Compare
…aphs-good#938 port) Port PR egraphs-good#938's multi-value merge machinery onto the shared backend interface, with no behavior change. Adds MergeFn::{OldCol, NewCol, Columns} to egglog-backend-trait; the bridge SchemaMath gains a key/value column split (n_keys/n_vals derived from the merge) + a per-column merge callback with a single-output fast path. Unifies the merge-target-UF mechanism: MergeFn::UnionIntoUf is now the single source of truth on all four backends (read at add_table; register_native_merge_view demoted to a consistency assertion), replacing flowlog/feldera's schedule-time native_merge_uf population. Dataflow/SQL backends collapse Columns([single]) to single-output; multi-value Columns is a documented unsupported error there pending tuple-output table support. New variants are wired but not yet emitted by the encoder. Bit-exact across all backends; single-output hot path perf-flat (ratio 0.994/0.997). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…oof-half machinery) Add the merge machinery for the bridge proof-half native :merge (not yet emitted by the encoder). MergeFn::UnionIntoUfWithProof composes a congruence edge proof (Trans(larger_pf, Sym(smaller_pf)), matching @congruence_rule's orientation bit-exactly) and stages the 4-column [lhs, rhs, proof, ts] insert into the proof-mode @UF_Sf; EclassMinProof returns the term proof of the surviving (min) eclass. Bridge implements them; duckdb/feldera/flowlog get inert arms (proof-mode native-merge is bridge-only). Additive/unreachable until the encoder emits the tuple-output proof view (next: port PR egraphs-good#938 frontend multi-output). No behavior change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ut merge) Port the front-end half of egglog PR egraphs-good#938: a function may declare multiple output sorts, stored as separate value columns, with per-column :merge (values e0 e1 ...) referencing old0/new0/old1/new1. Adds Schema.extra_outputs, FuncType/ResolvedSchema multi-output, ResolvedCall::Values, (values ...) parse/lowering in facts and set actions, per-column merge typechecking, and multi-output extraction. The (values ...) merge lowers to the existing MergeFn::Columns. Tuple outputs are restricted to plain functions (rejected for constructors, view tables, and the term/proof encoding -- the encoder-minted proof view exception comes next). Ports tests/tuple_outputs.rs (11/11). Single-output path unchanged; bit-exact sweep across normal/term/native-merge/proofs shows zero regression. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…arse regression) The PR egraphs-good#938 frontend port (0166a2d) added a RESERVED_KEYWORDS list reserving every command/action/schedule head (rewrite, birewrite, rule, set, ...) as a name, plus a ':'-prefix check on the expression-var path. This broke 56/106 Herbie sample100 files: Herbie names a ruleset 'rewrite' and the experimental run-with ... :until scheduler needs ':until' as a var. Command heads are recognized by position, so they never needed name-reservation; only 'values' (the tuple-output keyword) genuinely needs it. Reduce RESERVED_KEYWORDS to ["values"]; move the ':'-prefix rejection to definition-name + let-binder sites only. Restores 106/106 sample100 parse (was 50/106); tuple_outputs 12/12; no other regression. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ports egraphs-good/egglog#938 ("Add tuple-output functions") into the vendored egglog/ subtree. A function may declare more than one output sort, stored as separate value columns (no boxing); outputs are destructured in queries, written in actions, and merged per column with a (values ...) clause. Restricted to plain functions and unsupported by the term/proof encoding. The upstream diff applied cleanly except for one context conflict in src/proofs/proof_encoding_helpers.rs (this fork carries an extra NaiveEqSortPrimitiveFact reason); the TupleOutputFunction variant and guard were reapplied by hand. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds tuple-output functions as a first-class alternative to the implicit pair-unboxing in #933.
A function may declare more than one output sort:
Outputs are stored as separate value columns (no boxing); the functional dependency maps the keys to a tuple of values. They are:
(= (values lo hi) (interval x))(set (interval x) (values 0 100))(values e0 e1 …)clause whereeimerges columni, referencing the bound variablesold0,new0,old1,new1, …This collapses analyses that previously needed one function per output (e.g. the two-function interval analysis in
tests/interval.egg) into a single function.Implementation
egglog-bridge:SchemaMathnow tracks key vs. value columns independently; new per-columnMergeFn::ColumnswithOldCol/NewColleaves.core-relationsalready supported multi-value tables.Schema/FuncType/ResolvedSchemacarry extra output sorts; newResolvedCall::Valuesandvalueslowering in facts andsetactions; per-column merge type-checking.Error::UnsupportedProofCommand).Tests
tests/tuple_outputs.rscovers the interval analysis, column-independent merge, rule destructuring, eq-sort outputs with rebuild, and the error cases. Full suite green (filessnapshot suite: 744 passed),fmt/clippy -D warningsclean.🤖 Generated with Claude Code