Repository navigation
Conversation
…as one arrow `sum(x, by=l, over=a, into=b)` is written `sum(x, by=l, over=a -> b)`, and `into=` is gone. `over=` keeps its one meaning, what leaves the frame, and an arrow adds where it lands. Each end is a bare name or a bracketed list, as the two keywords took before. The short forms are unchanged: `over=a` where the declaration decides the landing, and `by=l` alone where it decides both. An arrow is a node with two children rather than a pair of strings, so a macro formal at either end is bound like any other. `at` and the partitions take no arrow, and say so with the column to write instead. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018uvytQ8L5Kz5s5tZYpkwpp
…ocs already call it `ArrowNode` is `DirectionNode`, and every message, page and test id says direction. The reference already said an operator walks a table "in the direction the call names", so the PR introduced a second word for one thing. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018uvytQ8L5Kz5s5tZYpkwpp
…the language now does Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018uvytQ8L5Kz5s5tZYpkwpp
…left end is a dimension `sum(p, by=zone_of, over=generator)` is refused toward `over=generator -> zone`: a call names the whole direction or none of it, so a bare `over=` is only ever a reduction over a dimension, and the shape of the value says which reading applies. The left end of a direction names the dimension a key column is over, since a key has one column per dimension, and the right end names columns, which may share one. A sum that consumed a value column was refused as a read; that refusal is now the left end's, and it still names the at() to write. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018uvytQ8L5Kz5s5tZYpkwpp
…elation(a -> b) `sum(p, by=zone_of, over=generator -> zone)` is `sum(p, by=zone_of(generator -> zone))`, `at(cap, by=ends, over=bus0)` is `at(cap, by=ends(bus0 -> line))`, and `shift(x, along=t, by=cal, within=week)` is `shift(x, along=t, by=cal(week))`. `by=` is the one relational keyword: it names the table, and the parenthesis holds what the call says about the walk through it — both ends for a sum and a read, the group alone for a partition, whose other end is `along=`. A call writes the whole walk or nothing. `over=` and `along=` keep their one meaning, and a sum with a `by=` takes no `over=`. Two defaults become rules, as `sum(x)` takes every dimension: a bare sum lands on every value column, and a bare read consumes every value column the operand carries. The relation and both ends are nodes, so a macro binds a formal at any of the three. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018uvytQ8L5Kz5s5tZYpkwpp
Documentation build overview
10 files changed ·
|
…e walk is the traversal it names The walk is the traversal, with its consumed, joined and produced columns, which is what the reference and `Walk` already call it. What the call writes after the relation, `a -> b`, is the walk's direction. Every message, page, docstring and test id that said "walk" for the written value now says direction. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018uvytQ8L5Kz5s5tZYpkwpp
…te way from the function the math prints Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018uvytQ8L5Kz5s5tZYpkwpp
…n-498nwi-walk Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018uvytQ8L5Kz5s5tZYpkwpp
…he whole key A read lands on the whole key, and the declaration says what that key is, so `at(cap, by=ends(bus0))` carries the direction on its own. One end inside the parenthesis now means the same thing on a read as on a partition: the columns read or grouped by, with the other end taken from the declaration or from `along=`. A sum still names both ends, since either of them can vary. The longer `at(cap, by=ends(bus0 -> line))` still loads, and prints the same math. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016enncWLMR8XjuSQ8JBKXGn
Contributor
Author
|
Closed in favor of #540 Will revisit for syntax discussions |
This was referenced Sep 21, 2026
Contributor
Author
|
Superseeded by #596 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
The following content was generated by AI.
What this changes
by=is the only relational keyword. It names a relation, and the direction of the walk through it is written after the name,relation(a -> b).into=,within=and the direction form ofover=are gone.sum(p, by=zone_of, over=generator, into=zone)sum(p, by=zone_of(generator -> zone))sum(p, by=gen_bt, into=[bus, technology])sum(p, by=gen_bt), or written out,by=gen_bt(generator -> [bus, technology])sum(f, by=ends, over=line, into=bus1)sum(f, by=ends(line -> bus1))at(cap, by=ends, over=bus0)at(cap, by=ends(bus0))at(tech_cap, by=gen_bt, over=[bus, technology])at(tech_cap, by=gen_bt)shift(x, along=t, by=cal, within=week)shift(x, along=t, by=cal(week))position(g, by=gen_bt, within=[bus, technology])position(g, by=gen_bt([bus, technology]))sum(p, by=gen_bus),at(price, by=gen_bus),by=[gen_bus, gen_tech]The rules.
by=means the declaration decides the whole direction. A sum names both ends, since either can vary:sum(p, by=zone_of)with two key columns is refused towardby=zone_of(<dimension> -> zone). A read and a partition name one — the columns read, or the columns grouped by — because the other end is not theirs to choose. A read lands on the whole key, and a partition takes its other end fromalong=.generatornames the key column over it, and a renamed key column is written by its dimension. Value columns may share a dimension, sobus1is named by column.over=andalong=keep one meaning.over=is a reduction over a dimension,along=the axis a translation moves along. A sum with aby=takes noover=; the call shape refuses both together.sum(p, by=gen_bt)ontobus × technology), and a bare read consumes every value column the operand carries (at(tech_cap, by=gen_bt)). Both were refused before. Each is the rulesum(x)already follows for dimensions, and each is refused where two value columns share a dimension, naming the column to write.sum(x, by=l(a -> b))withl=,a=andb=at the call.Nothing changes for a consumer:
Walk,GroupSum,Atand the partitions lower as before. A read's split into consumed, joined and produced isWalk.read_by, one rule the dim check and the lowering share. The typesetter's math did not move; the golden.outfiles are unchanged.Why
Two keywords for one direction, with the table named in a third, made the relational calls hard to read, and
over=meant a dimension in one call and a column in the next. Writing the direction after the relation puts the table first, shows the direction unmistakably, and leaves every keyword with one meaning. The probe rows on the operators page show each form beside the math it prints, which is where to judge it.Why one end on a read, added after #515
The read was the one place this shape cost more than the keywords it replaced.
at(cap, by=ends(bus0 -> line))writes a landing that the declaration already fixes — a read lands on the whole key, always — so the arrow's right side could only ever be right or refused. #515'sby=ends.bus0is shorter for exactly that reason.One end already meant something on a partition,
by=cal(week): the columns the group is read from, with the other end coming fromalong=. Giving it the same meaning on a read makes the parenthesis one rule rather than two, and the read as short as #515's without leaving this PR's shape.The math is the check that nothing was lost. The operator probe's cell is byte-identical before and after —$f_l \le \mathrm{cap}_{\mathrm{ends.bus0}(l)}$ , exactly as the long form did.
at(cap, by=ends(bus0))printsRefusals, each naming the rewrite
sum(p, by=zone_of)with two key columns:'zone_of' has 2 key columns (['generator', 'period']), and the call has to say the direction: by=zone_of(<dimension> -> zone).sum(p, by=lk(h)):by=lk(h) names one end, and a sum names both: what leaves and what arrives. Write by=lk(<dimension> -> h).sum(p, by=lk(g -> h), over=g):sum() takes at most one of by= or over= — a relation carries its own dimensions, and a direction names what leaves inside by=, so over= has nothing to add.sum(p, by=lk(h -> g))wherehis a value column's dimension:a sum consumes key columns, and 'lk' holds 'h' as a value column. To read it, write at(..., by=lk(h)).sum(p, by=lz)withh0,h1over one dimension:by=lz lands on ['h0', 'h1'], two columns over ['h'] …at(r, by=lk(g)), naming a key column as the column read:by=lk(g) reads the key column(s) ['g'], and at reads value columns at the key. Read a value column, among ['h'], or walk the other way with sum.at(r, by=lk(h -> g))with key[g, z]:lands on ['g'], and a read lands on the whole key, ['g', 'z']. Write by=lk(h), which names the columns read and lands on the key, or write that key out.at(cap, by=ends):'ends' has two value columns over ['bus'] (['bus0', 'bus1']), so nothing says which one is read. Name it: by=ends(bus0).shift(..., by=lz(g -> h)):a partition walks the axis along= names, and the parenthesis names only the columns its group is read from. Write by=lz(h).sum(p, over=lk(h -> g)):names a direction through a relation, and a direction belongs in by=. Write sum(<expr>, by=lk(h -> g)).into=,within=: the closed call shape.by=l(a, b -> c)is a parse error, since a comma separates arguments; brackets for a list at an end.Guards and their tests
Each guard deleted in turn, its test run, all failing without it. The two the read's change touches were re-checked in the session that shortened it; the rest were checked when they landed:
_walk) →-k a-sum-with-one-end-written_read) →-k a-read-landing-short-of-the-key_read) →-k a-read-naming-a-key-column_read) →-k bare-read-through-two_partition_walk) →-k partition-with-a-left-endover=(_dim_ref) →-k a-direction-in-over_arith) →-k direction-bound-to-a-formal_key_columns_over) →-k "key-is-not-over or walks-to-the-key"Coverage moved when the read's one-end refusal went:
a-read-with-one-end-writtenis deleted, sinceat(r, by=lk(h))now loads, anda-read-naming-a-key-column-as-the-column-it-readstakes over the rule that the named column is a value column. Two rows were added totest_dim_inference,at(bz, by=gen_bz([bus, zone]))andat(load, by=pair(b0)); both failed on the tree before the change, with the refusal the change deletes.Verified
pixiis not installable in this session (the installer is blocked by the proxy with a 403), so the gates ran from auvvenv on Python 3.13 with the pinnedruff==0.16.1andpyrefly==1.2.0:pytest -q -n 4: 1317 passed, 1 skipped (the whole suite,test_docs.pyincluded).ruff check,ruff format --check: clean.prettier --checkon the changed pages: clean.reuse lint: compliant, 212/212.typos: clean.pyrefly check: 11 errors, 10 suppressed — the same count on the unmodified tree, and every one is a missing stub orpydanticimport this venv lacks rather than a finding about the diff.tests.typesetting.golden,tools.notation,tools.spec_math,tools.gallery,tools.home_math,tools.schema. The.outfiles,docs/reference/notation.md,docs/index.mdand the README did not move at all, so the printed math is identical.docs/reference/language/operators.mdanddocs/examples/operators.mdmove only in the probe's label and its model.mkdocs build: no link or anchor warning.--strictaborts here on fetchingdocs.python.org/objects.invthrough the proxy, which is the environment, not a page.Not run:
pixi run cias one command,zizmor,taplo,compile-tex.Deliberately not done: refusing the long
at(cap, by=ends(bus0 -> line))now that one end says the same thing. Two spellings for one read is a question worth deciding — the shorter is what the docs and the probe teach, and the longer shows the reader of a file what the result is over. Say the word and it becomes one spelling.🤖 Generated with Claude Code
https://claude.ai/code/session_016enncWLMR8XjuSQ8JBKXGn