Skip to content

feat(language): a sum and a read name their direction in direction=, as a -> b - #502

Closed
FBumann wants to merge 9 commits into
claude/vigilant-allen-498nwifrom
claude/vigilant-allen-498nwi-direction
Closed

FBumann wants to merge 9 commits into
claude/vigilant-allen-498nwifrom
claude/vigilant-allen-498nwi-direction

Conversation

@FBumann

@FBumann FBumann commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

Prompt: "I think we should actually do another PR, that is the same as #495, but with this change. And du "within". Only use direction for the arrow notation!" — the change being direction=a -> b in place of over=a -> b. Then: "I thought at has a direction?" — it does, and it is written the same way. Stacked on #493, which is the base branch. An alternative to #495 and #499, not a stack on either.

Note

The following content was generated by AI.

What this changes

#495 with the direction under a keyword of its own, on the read as well as the sum. into= is gone, and a walk through a relation writes its direction as direction=a -> b: what leaves the operand on the left, what arrives on the right. over= is only ever a reduction over a dimension.

before now
sum(p, by=zone_of, over=generator, into=zone) sum(p, by=zone_of, direction=generator -> zone)
sum(p, by=zone_of, over=generator) sum(p, by=zone_of, direction=generator -> zone)
sum(p, by=gen_bt, into=[bus, technology]) sum(p, by=gen_bt, direction=generator -> [bus, technology])
sum(f, by=ends, over=line, into=bus1) sum(f, by=ends, direction=line -> bus1)
at(cap, by=ends, over=bus0) at(cap, by=ends, direction=bus0 -> line)
at(tech_cap, by=gen_bt, over=[bus, technology]) at(tech_cap, by=gen_bt, direction=[bus, technology] -> generator)
sum(p, by=gen_bus), at(price, by=zone_of), shift(..., by=cal, within=week) unchanged

Three rules:

  • Every keyword has one kind of value. over= is a dimension, by= a relation, direction= an arrow. A sum with a by= takes no over=, and at takes none at all. The partitions keep within=, since a group is not a direction.
  • A call writes the whole direction or nothing. sum(p, by=gen_bus) and at(price, by=zone_of) where the declaration decides both ends; direction=a -> b where it does not. A bare name in direction= is refused toward the arrow, and sum(p, by=zone_of) with two key columns toward direction=<dimension> -> zone.
  • The left end of a sum names a dimension, every other end names columns. A key has one column per dimension, so generator names the key column over it. Value columns may share a dimension, so bus1 is named by column. A read's right end is the whole key, written out, and its direction runs from value to key, the opposite way round from the function the math prints, ends.bus0(l). The docs say so.

The direction is a node with two children, so a macro formal at either end is bound like any other. Nothing changes for a consumer, and the typesetter's math did not move.

Why

The same case as #495, without its one remaining overload: there, over= was a dimension in one call and a direction in the next, and the shape of the value told them apart. Here the keyword tells them apart. Against #499, nothing looks like a call, at the cost of a third keyword on every written direction.

Refusals
  • sum(p, by=lk, over=g): sum() takes at most one of by= or over= — a relation carries its own dimensions, and direction= names what leaves beside by=, so over= has nothing to add.
  • sum(p, by=lk, direction=g): direction= names both ends, what leaves and what arrives. Write direction=g -> h, or drop direction= where the declaration decides both ends.
  • at(r, by=lk, direction=h): direction= names both ends, the value column read and the key it lands on. Write direction=h -> g.
  • at(r, by=lk, direction=h -> g) with key [g, z]: lands on ['g'], and a read lands on the whole key, ['g', 'z']. Write direction=h -> [g, z].
  • 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: direction=<dimension> -> zone.
  • sum(p, over=h -> g): over= names a dimension, and a direction belongs in direction=. Write sum(<expr>, by=<relation>, direction=h -> g).
  • direction=h -> g on a sum where h is 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, direction=h -> g).
  • within=g -> z: names the columns a group is read from, and a partition has no direction: it walks the axis along= names. Write within=z.
  • into=, and over= on at: the closed call shape.
Verified

pixi is not installable in this session (the installer is blocked by the proxy with a 403), so the gates ran from a uv venv on Python 3.13 with the pinned ruff==0.16.1 and pyrefly==1.2.0:

  • pytest -q -n 4: 1304 passed, 1 skipped (the whole suite, test_docs.py included).
  • ruff check, ruff format --check: clean. pyrefly check: 0 errors.
  • prettier --write on the changed pages and probes.
  • Generated files regenerated with their tools: tests.typesetting.golden, tools.notation, tools.spec_math, tools.gallery, tools.home_math, tools.schema. The .out files did not move.
  • The sum's guards are feat(language): a sum through a relation names the direction of its walk as over=a -> b #495's, verified there by deletion; the read's two, one end written and landing short of the key, each have a test in test_validation.py.

Not run: pixi run ci as one command, typos, reuse lint, zizmor, taplo, compile-tex.

🤖 Generated with Claude Code

https://claude.ai/code/session_018uvytQ8L5Kz5s5tZYpkwpp

…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
…tion=, as a -> b

`sum(p, by=zone_of, over=generator, into=zone)` is written
`sum(p, by=zone_of, direction=generator -> zone)`, and `into=` is gone. The
direction has a keyword of its own, so `over=` is only ever a reduction over
a dimension, and a sum with a `by=` takes no `over=`. A call writes the whole
direction or nothing: `sum(p, by=gen_bus)` where the declaration decides both
ends. The left end names a dimension, the right end columns, which may share
a dimension. `at` keeps `over=<column>` and the partitions keep `within=`,
since neither has an arrow.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018uvytQ8L5Kz5s5tZYpkwpp
@read-the-docs-community

read-the-docs-community Bot commented Sep 16, 2026 •

Copy link
Copy Markdown

…e value column to the whole key

`at(cap, by=ends, over=bus0)` is `at(cap, by=ends, direction=bus0 -> line)`,
so `over=` leaves `at` as well and is only ever a reduction over a dimension.
A read's direction runs from value to key, the opposite way round from the
function the math prints, and the docs say so. The partitions keep `within=`,
since a group is not a direction.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018uvytQ8L5Kz5s5tZYpkwpp
@FBumann FBumann changed the title feat(language): a sum through a relation names its direction in direction=, as a -> b feat(language): a sum and a read name their direction in direction=, as a -> b Sep 16, 2026
…n-498nwi-direction

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018uvytQ8L5Kz5s5tZYpkwpp
@FBumann FBumann added the area: relations relations and dimensions: the relation design label Sep 17, 2026
@FBumann FBumann added the proposal: direction keyword The direction gets a keyword of its own, direction=generator -> zone (#502) label Sep 17, 2026 — with Claude
@FBumann

FBumann commented Sep 18, 2026

Copy link
Copy Markdown
Contributor Author

Closed in favor of #534

Will revisit for syntax discussions

@FBumann FBumann closed this Sep 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: relations relations and dimensions: the relation design proposal: direction keyword The direction gets a keyword of its own, direction=generator -> zone (#502)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants