Skip to content

Pure functions: parts of the body without slots are not walked - #2102

Merged
rocky merged 4 commits into
masterfrom
replace-slots-skip-slot-free
Nov 4, 2026
Merged

rocky merged 4 commits into
masterfrom
replace-slots-skip-slot-free

Conversation

@tombouctou

Copy link
Copy Markdown
Collaborator

Applying a pure function replaces its slots with replace_slots, which went over every subexpression of the body. Parts without slots were already kept as they are (#2085), but they were still walked. In Combinatorica's code, a graph is passed to a function and written into a pure function inside it. That graph was walked once per application, so mapping over its own elements cost the square of its length.

Change

  • An expression knows whether a Slot or SlotSequence occurs in it, heads included (f[#][x] has one in its head).
  • This is worked out once and kept with the expression, and forgotten when its head or elements change.
  • replace_slots returns a part without slots at once.

Measurement: five applications of {#1, Length[big]} & with a 300-element big make 3020 replace_slots calls on master, now 15.

Tests: test/core/test_replace_slots.py.

  • The call count above.
  • Pure functions with lists in their bodies, ##, nested Function, Hold.
  • Slots in compound heads at any depth: #[0] &, {a, {#[0]}} &, f[#][x] &, g[h[#]][x] &, f[##][y] &.

The full pytest run and the doctests pass.

🤖 Generated with Claude Code

tombouctou and others added 4 commits November 4, 2026 18:24
Applying a pure function replaces its slots with replace_slots, which
went over every subexpression of the body. Parts without slots were
already kept as they are (#2085), but still walked: a list passed to a
function and written into a pure function inside it, like a graph in
Combinatorica's code, was walked once per application, so mapping over
its own elements cost the square of its length.

Now an expression knows whether a Slot or SlotSequence occurs in it,
heads included (f[#][x] has one in its head): worked out once and kept
with it, forgotten when its head or elements change. replace_slots
returns a part without slots at once.

Five applications of {#1, Length[big]} & with a 300-element big make
3020 replace_slots calls on master, now 15.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rocky

rocky commented Nov 4, 2026

Copy link
Copy Markdown
Member

LGTM

@rocky
rocky merged commit 3efb132 into master Nov 4, 2026
14 of 15 checks passed
@rocky
rocky deleted the replace-slots-skip-slot-free branch November 4, 2026 18:13
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.

2 participants