Nested dip fails type-check; row-poly constraints don't propagate into quotation bodies #471
Labels
No labels
bug
dependencies
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
refactor
rust
technical-debt
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
navicore/patch-seq#471
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Nested
dip(the classic Forth/Factor idiom for reaching deeper than one stack item) fails the type checker in v7.0, even when the runtime semantics are sound and the user is being careful.The "sequential double dip" form fails for the same reason:
(That second form lints clean but doesn't actually dip past two items — it modifies the wrong slot. So neither shape of nested dip behaves the way Forth/Factor users expect.)
Root cause
dip's effect is( ..a x [..a -- ..b] -- ..b x ). When the typechecker walks the body of the outer quotation[ [ 50 i.+ ] dip ], the surrounding stack is row-polymorphic — the row variable..ais unconstrained. The innerdiprequires "a value below the quotation", but with..aunconstrained the checker can't prove there is one, so it conservatively rejects.The semantics are fine — at runtime the outer dip would supply the value below — but the row-variable inference doesn't propagate that constraint into the quotation body.
What works today
The user can hide one value with
dipand rearrange inside the quotation:This lints clean and behaves correctly. It's the workaround currently used in seqlings (chapter 37 exercise 04).
Why fix it
dip dipis a foundational combinator pattern. It's how2dip,3dip, and friends are typically built in Forth and Factor. Without it:dipbut can't teach "reach deeper with nested dip" — the most natural follow-up exercise.2dip = swap dip swapor[ q ] 2dip = [ [ q ] dip ] dip) ends up requiring ad-hoc workarounds.Suggested implementation paths
A few approaches, ranked by ambition:
Add
2dip(and maybe3dip) as builtins with concrete row-poly signatures. The typechecker only has to handle the builtin's published effect, not infer through nested quotation bodies. Cheapest fix; covers the common cases without changing inference. (Factor and Joy ship these.)Improve row-variable inference inside quotation bodies. When a quotation body refers to a row variable from its enclosing scope, propagate constraints introduced by builtins (e.g., "this body uses
dip, which requires..ato have at least one element"). Larger change; benefits every combinator, not justdip.Document the limitation. Add a section to docs/language-guide.md explaining that nested dip doesn't type-check and showing the swap-inside-dip workaround. Cheapest; concedes the rough edge instead of fixing it.
(1) is probably the right pragmatic step. (2) is the principled fix.
Cross-reference
04-dip-deeper) was rewritten away from[ [ q ] dip ] dipto[ swap q swap ] dipbecause the nested form doesn't compile. If2dipis added, that exercise can be restored to its natural shape.#472