Skip to content

Add tuple-output functions#938

Draft
oflatt-claude wants to merge 2 commits into
egraphs-good:mainfrom
oflatt-claude:tuple-outputs
Draft

Add tuple-output functions#938
oflatt-claude wants to merge 2 commits into
egraphs-good:mainfrom
oflatt-claude:tuple-outputs

Conversation

@oflatt-claude

Copy link
Copy Markdown
Contributor

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:

(function interval (Math) (i64 i64)
  :merge (values (max old0 new0) (min old1 new1)))   ; col 0 = lo, col 1 = hi

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: (= (values lo hi) (interval x))
  • written in actions: (set (interval x) (values 0 100))
  • merged with a (values e0 e1 …) clause where ei merges column i, referencing the bound variables old0, 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: SchemaMath now tracks key vs. value columns independently; new per-column MergeFn::Columns with OldCol/NewCol leaves. core-relations already supported multi-value tables.
  • front-end: Schema/FuncType/ResolvedSchema carry extra output sorts; new ResolvedCall::Values and values lowering in facts and set actions; per-column merge type-checking.
  • Tuple outputs are restricted to plain functions (not constructors/relations/view tables) and are not supported by the term/proof encoding (reported cleanly via Error::UnsupportedProofCommand).

Tests

tests/tuple_outputs.rs covers the interval analysis, column-independent merge, rule destructuring, eq-sort outputs with rebuild, and the error cases. Full suite green (files snapshot suite: 744 passed), fmt/clippy -D warnings clean.

🤖 Generated with Claude Code

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>
@oflatt-claude oflatt-claude requested a review from a team as a code owner June 26, 2026 18:15
@oflatt-claude oflatt-claude requested review from ezrosent and removed request for a team June 26, 2026 18:15
@oflatt oflatt marked this pull request as draft June 26, 2026 18:19
@codecov-commenter

codecov-commenter commented Jun 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.21739% with 54 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.72%. Comparing base (e22580d) to head (d7a1ba2).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
src/lib.rs 75.47% 13 Missing ⚠️
src/core.rs 81.81% 12 Missing ⚠️
src/ast/mod.rs 91.89% 9 Missing ⚠️
egglog-bridge/src/lib.rs 92.77% 6 Missing ⚠️
src/typechecking.rs 95.53% 5 Missing ⚠️
src/ast/parse.rs 94.91% 3 Missing ⚠️
src/proofs/proof_checker.rs 0.00% 2 Missing ⚠️
src/exec_state.rs 0.00% 1 Missing ⚠️
src/extract.rs 90.90% 1 Missing ⚠️
src/proofs/proof_encoding_helpers.rs 75.00% 1 Missing ⚠️
... and 1 more
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@codspeed-hq

codspeed-hq Bot commented Jun 26, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 5.18%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 1 improved benchmark
✅ 35 untouched benchmarks
⏩ 226 skipped benchmarks1

Performance Changes

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)

Open in CodSpeed

Footnotes

  1. 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.

@oflatt-claude oflatt-claude force-pushed the tuple-outputs branch 2 times, most recently from 0ad1048 to 3197ad2 Compare June 26, 2026 21:49
`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>
oflatt added a commit to oflatt/egglog-duckdb that referenced this pull request Jun 30, 2026
…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>
oflatt added a commit to oflatt/egglog-duckdb that referenced this pull request Jun 30, 2026
…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>
oflatt added a commit to oflatt/egglog-duckdb that referenced this pull request Jun 30, 2026
…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>
oflatt added a commit to oflatt/egglog-duckdb that referenced this pull request Jun 30, 2026
…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>
oflatt-claude pushed a commit to oflatt-claude/egglog-encoding that referenced this pull request Jul 7, 2026
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants