A pattern reaches inside a binder, and only one node type needed it (#1074) - #1122
Merged
Merged
Conversation
`{ x : x in S }` is `S`, and the `switch` said so by deconstructing
`ConditionalSet(var v, Inf(var v, var s))` -- reading the node's stored parts. A pattern could not.
The matcher walked `DirectChildren`, where a set builder publishes one child, its predicate, with
the bound name already replaced by a placeholder invented per traversal: `{ x : x in [0; 1] }`
offers `%1 in [0; 1]` and nothing else. So a two-child pattern over it never matched, and no
pattern could name the bound variable at all. The rule bound the whole set and took it apart in
its replacement -- a rule whose shape was code again.
The rename is load-bearing. It is what makes traversal alpha-invariant, and an earlier issue was
about that placeholder escaping into `Vars` and `FreeVariables`, so the fix is not to stop
renaming. Matching reads the declared pair instead, through `Entity.MatchableChildren` --
`DirectChildren` for every node but this one. That is the same split `VarsAndConsts` and
`FreeVariables` already make, and for the same reason: what a set builder *is* is read off its
declared parts, and what it publishes for traversal is a capture-avoiding rewriting of them.
**Only `ConditionalSet` needed it.** The issue expected every binder to be in the same position and
named six. Measuring them says otherwise: a summation and a product offer four children with the
index second, a limit and a derivative three, an integral two or four, a lambda two -- every one of
them un-renamed, so an ordinary `Node<T>` already reaches the bound name and a repeated hole already
says "the same variable". One override, not a family of them, and
`EveryOtherBinderPublishesTheNameItBinds` is the measurement in the build so that a binder which
starts hiding its name fails rather than going quiet.
`MatchPattern.Binder<T>(varName, body)` is the spelling. The name position is a hole and cannot be
anything else: matching a bound name against a written one would make `{ x : ... }` and
`{ y : ... }` different expressions, which they are not. Alpha-invariance then holds for the reason
it holds of the `switch` arm -- what is asserted is that two occurrences are the same name, never
which name.
A binder pattern reports `CanEMatch` false rather than relying on never being asked. The e-graph is
built from `DirectChildren` and has no notion of binding, which is why `MatchPattern.Construct` has
no entry for a binder in the first place.
The rule is data now, and its reason for being one-way got better rather than going away: it was
`ReplacementIsCode` and is `ReplacementDropsHoles` -- one-way because `S` does not say which name a
set builder over it would bind, rather than because of how it was written.
`EveryDataRuleIsBuildableOnBothSides` asserted that `PatternCannotBeBuilt` is exactly the rules with
an unbuildable left. `Classify` returns the *first* reason a rule is one-way, so a rule that both
drops a hole and has an unbuildable left is reported as dropping the hole -- which this rule is, and
which nothing had been before. Stated as the two true implications instead.
Part of #1074.
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.
Closes #1074.
The defect
{ x : x in S }isS, and theswitchsaid so by deconstructingConditionalSet(var v, Inf(var v, var s))— reading the node's stored parts. A pattern could not. The matcher walkedDirectChildren, where a set builder publishes one child, its predicate, with the bound name already replaced by a placeholder invented per traversal:So a two-child pattern over it never matched, and no pattern could name the bound variable at all. That rule bound the whole set and took it apart in its replacement — honest, but a rule whose shape was code again, so it could not be read backwards or checked the way its neighbours are.
The rename is load-bearing: it is what makes traversal alpha-invariant, and #1008 was about that placeholder escaping into
Vars/FreeVariables. So the fix is not to stop renaming.The fix
Matching reads the declared pair, through a new internal
Entity.MatchableChildren—DirectChildrenfor every node but one. This is the same splitVarsAndConstsandFreeVariablesalready make, and for the same reason: what a set builder is is read off its declared parts, and what it publishes for traversal is a capture-avoiding rewriting of them.MatchPattern.Binder<T>(varName, body)is the spelling, and the rule becomes:The repeated
"v"is what theswitchwrote aswhen v1 == v1a.The issue's premise was wrong, and that shrank the fix
#1074 said "Every binder is in the same position:
sum,product,integral,derivative,limit,ConditionalSet" and expected aBinder<T>family. Measured, that is false — I had generalised from the one case I had opened:DirectChildren{ x : x in [0; 1] }%1 in [0; 1]lambda(x, x + 1)x|x + 1sum(x, x, 1, 3)x|x|1|3product(x, x, 1, 3)x|x|1|3integral(x, x)x|xintegral(x, x, 0, 1)x|x|0|1derivative(x, x)x|x|1limit(x, x, 0)x|x|0Every binder but the set builder publishes the name it binds as an ordinary child, un-renamed — so a plain
Node<T>already reaches it and a repeated hole already says "the same variable". One override, not six.EveryOtherBinderPublishesTheNameItBindsputs that measurement in the build, so a binder that starts hiding its name fails here rather than going quiet.Alpha-invariance
The name position is a hole and cannot be anything else — matching a bound name against a written one would make
{ x : … }and{ y : … }different expressions, which they are not. What is asserted is that two occurrences are the same name, never which. Pinned byTheBoundNameIsNotWhatIsMatchedOn.The widening also must not hand every two-child pattern in the library a new node to match. All three matching entry points check the node type first, and
ATwoChildPatternOfAnotherTypeStillDoesNotMatchASetBuilderasks all three rather than the one a rewrite pass happens to use.A binder pattern reports
CanEMatchfalse rather than relying on never being asked: the e-graph is built fromDirectChildrenand has no notion of binding, which is whyMatchPattern.Constructhas no entry for a binder to begin with.One test assertion was wrong in general
EveryDataRuleIsBuildableOnBothSidesassertedLeft.IsBuildable == (Reversal is not PatternCannotBeBuilt).Classifyreturns the first reason a rule is one-way, so a rule that both drops a hole and has an unbuildable left is reported as dropping the hole. This rule is the first of that shape. Stated as the two true implications instead.Its reason for being one-way got better rather than going away:
ReplacementIsCode→ReplacementDropsHoles— one-way becauseSdoes not say which name a set builder over it would bind, rather than because of how it was written.Measured
No answer changes:
MatchableChildrenis internal and only the node pattern reads it, and the rule fires on exactly what it fired on before —MatchedRulesAgreeWithTheSwitchTestcompares the data rules against the originalswitchand is unchanged. NoBREAKING-CHANGES.mdentry is owed.Failed: 0, Passed: 9097, Skipped: 14locally on net10.0.🤖 Generated with Claude Code
https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura