Skip to content

A bound index is not a free variable (#1019) - #1045

Merged
Rafael-SOWNet merged 1 commit into
masterfrom
fix/free-variables-under-a-range-binder
Aug 23, 2026
Merged

Rafael-SOWNet merged 1 commit into
masterfrom
fix/free-variables-under-a-range-binder

Conversation

@Rafael-SOWNet

Copy link
Copy Markdown
Member

FreeVariables knew about two binders — Lambda and the set builder — and about no others. A
summation and a product bind their index, and a definite integral binds its variable between its
limits. All three reported the bound name as free.

Measured on a build of 2.3.0 and a build of this branch:

input 2.3.0 now
sum(k, k, 1, n) { k, n } { n } Σ over k is a function of n alone
product(k, k, 1, n) { k, n } { n }
integral(t * b, t, 0, 1) { b, t } { b } limits bind t
integral(t * b, t) { b, t } { b, t } unchanged, on purpose
derivative(t * b, t) { b, t } { b, t } unchanged, on purpose
lambda(x, x + y) { y } { y }
{ k : k > a } { a } { a }

The unchanged rows are the interesting part

An indefinite integral does not bind: the antiderivative of t * b over t is
b * t ^ 2 / 2 + C, still a function of t. Nor does a derivative — d/dt denotes a function of
t. They look like the same shape as a summation and are not, and a later sweep that "completes"
the binder list by adding them would make them wrong. AnIndefiniteIntegralAndADerivativeDoNotBind
pins that, with the reason in the test.

Scope of the binding

The index is bound over the bounds as well as the body, so sum(k, k, k, n) is { n }. That is
not a new decision — Binding already says of itself that the
name a binder is handed "is honoured throughout it, through the summand and the bounds", and this
follows it rather than inventing a second answer.

Vars and VarsAndConsts are untouched

They mean every name occurring, bound ones included: "sum(k, k, 1, n)".ToEntity().Vars is still
{ k, n }. That is what their own XML example has always documented — it lists a lambda's parameter
under variables and constants. Occurring and free are different questions and the three properties
answer them separately. This corrects item 2 of #1019, which claimed VarsAndConsts was wrong for
lambda; it is not, and this is a one-property change rather than a three-property one.

Why this does not wait for a major version

#1019's own criterion is that a change belongs on the pending-breakages list when it moves the value
of existing input as a deliberate convention choice. This is a wrong answer becoming a right one —
a bound index was never free — which the same paragraph says has gone in a minor before.

Verification

  • 10 new cases in the existing FreeVariablesTest.cs, which already held the Lambda and set-builder
    cases from FreeVariables leaks a set builder's %1 placeholder, and only a lambda counts as a binder #989, so the binder story is in one place.
  • Full unfiltered suite: 8080 passed / 0 failed / 14 skipped, against 8070 on master.
    Exactly the cases added; no existing test depended on the old answers.
  • BREAKING-CHANGES.md entry with both values measured on a build of each side.

Answers Happypig375's question on #1019 — "identify the correct design here" — with the measurement
posted there.

FreeVariables knew about Lambda and the set builder and about no other binder.
A summation and a product bind their index, so sum(k, k, 1, n) is a function of
n alone, and a definite integral binds its variable between its limits. All
three reported the bound name as free.

The index is bound over the bounds as well as the body, which is what Binding
already says of itself -- the name a binder is handed is honoured throughout
it, through the summand and the bounds -- so sum(k, k, k, n) is { n } as well.

An indefinite integral and a derivative are left alone, and that is the part
worth pinning rather than the part worth fixing. The antiderivative of t * b
over t is b * t ^ 2 / 2 + C, still a function of t, and d/dt denotes a function
of t. They look like the same shape as a summation and are not; a sweep that
completes the binder list by adding them makes them wrong, so a test says so.

Vars and VarsAndConsts are untouched. They mean every name occurring, bound
ones included, which is what their own XML example has always documented by
listing a lambda's parameter under variables and constants.

Measured on a build of each side; 8070 -> 8080 passed, 0 failed, and no
existing test depended on the old answers.
@Rafael-SOWNet
Rafael-SOWNet merged commit 72c74ca into master Aug 23, 2026
31 checks passed
Rafael-SOWNet added a commit that referenced this pull request Aug 27, 2026
`FreeVariables` learned about a summation, a product and a definite integral in #1045,
and about `limit` not at all. That commit enumerates the indefinite integral and the
derivative as deliberate exclusions and does not mention a limit, so this was an omission
rather than a decision.

And the reason those two are excluded is what makes this one bind. An antiderivative of
`t * b` over `t` is `b * t ^ 2 / 2 + C` and `d/dt` denotes a function of `t` -- both are
still functions of the variable. **A limit never is.** `lim(t, t, 0)` is 0, and no limit's
value depends on the name it approaches along, so the variable is consumed exactly as a
summation index is. The destination is where the dependence goes and is bound over as
well, so `lim(t, t, b)` is a function of `b` alone.

`Limitf` is a `CalculusOperator(Expression, Var)`, the same shape as `Integralf`, so this
is one arm next to it -- unconditional where the integral's is conditional, which is the
distinction the comment now records.

Measured on a build of each side: `limit(t * b, t, 0).FreeVariables` was `{ t, b }` and is
`{ b }`. `Vars` and `VarsAndConsts` untouched.
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.

1 participant