RFC: Auxiliary stash stacks for complex stack juggling #350

Closed
opened 2026-02-05 03:47:11 +00:00 by navicore · 1 comment
navicore commented 2026-02-05 03:47:11 +00:00 (Migrated from github.com)

Summary

Should Seq support auxiliary "stash" stacks for temporary storage during complex stack manipulation?

Background

Classic Forth provides multiple stacks - the data stack, return stack, and sometimes separate numeric stacks. The return stack (>R/R>) is commonly used for temporary storage when stack juggling gets unwieldy.

This issue explores whether Seq would benefit from similar functionality, and what the design constraints should be.

Proposed Design

Stash Stacks (not execution stacks)

The key distinction: auxiliary stacks would be storage only, not execution contexts.

Feature Purpose Complexity
Main stack All execution happens here Already exists
Stash stacks Temporary storage within a word Proposed
Generators Suspended execution with own stack Already exists

Generators already handle the "suspended execution with arbitrary stack state" use case. Stash stacks would purely be for juggling convenience.

Scoped and Balanced

Auxiliary stacks must be empty when a word returns - enforced by the compiler:

: valid ( a b -- result )
  >aux          # stash b
  do-stuff
  aux>          # restore b - required!
  combine ;

: invalid ( a b -- result )
  >aux          # stash b
  do-stuff ;    # COMPILE ERROR: aux stack not empty at return

Single vs Named Stacks

Option A: Single auxiliary stack (simpler)

>aux aux> aux@

Option B: Named stacks (more flexible)

: complex ( a b c d e -- ... )
  >:first
  >:second
  ...
  :second>
  :first> ;

Named stacks would need scoping rules - probably lexical scope tied to word boundaries.

The Philosophical Question

Arguments Against (purity)

Concatenative languages derive power from having the stack as the entire program state. Every word is Stack → Stack. Stash stacks introduce hidden state:

aux>   # What does this produce? Depends on invisible state.

This is the same problem with variables that concatenative languages avoid. It breaks equational reasoning.

Arguments For (pragmatism)

  1. Forth precedent - >R/R> has existed since the beginning
  2. Still compositional - >aux is ( a -- ), aux> is ( -- a ), they compose like any word
  3. Scoped/balanced - No persistent state escapes, unlike let bindings
  4. Readability - Complex shuffles with 5+ items become unreadable with pure combinators

Comparison to let bindings

let binding Stash stack
Named reference Yes - x refers to value No - position-based
Scoped Yes - lexical scope No - must balance within word
Expression Binds result of expression Just moves existing value
Stack effect Hidden Explicit

Stash stacks are arguably less of a deviation than let because they're still operations, not bindings.

Open Questions

  1. Is the complexity tax worth it, or should complex shuffles signal need for refactoring?
  2. Single aux stack vs named stacks?
  3. Does this undermine Seq's concatenative identity?
  4. What's Seq's position on the purity vs pragmatism spectrum?
  • Forth's return stack (>R, R>, R@)
  • Factor's locals (optional, explicit opt-in)
  • Joy (stayed purely concatenative)

Captured from design discussion. No immediate action needed - for future consideration.

## Summary Should Seq support auxiliary "stash" stacks for temporary storage during complex stack manipulation? ## Background Classic Forth provides multiple stacks - the data stack, return stack, and sometimes separate numeric stacks. The return stack (`>R`/`R>`) is commonly used for temporary storage when stack juggling gets unwieldy. This issue explores whether Seq would benefit from similar functionality, and what the design constraints should be. ## Proposed Design ### Stash Stacks (not execution stacks) The key distinction: auxiliary stacks would be **storage only**, not execution contexts. | Feature | Purpose | Complexity | |---------|---------|------------| | Main stack | All execution happens here | Already exists | | Stash stacks | Temporary storage within a word | Proposed | | Generators | Suspended execution with own stack | Already exists | Generators already handle the "suspended execution with arbitrary stack state" use case. Stash stacks would purely be for juggling convenience. ### Scoped and Balanced Auxiliary stacks must be empty when a word returns - enforced by the compiler: ```seq : valid ( a b -- result ) >aux # stash b do-stuff aux> # restore b - required! combine ; : invalid ( a b -- result ) >aux # stash b do-stuff ; # COMPILE ERROR: aux stack not empty at return ``` ### Single vs Named Stacks **Option A: Single auxiliary stack (simpler)** ```seq >aux aux> aux@ ``` **Option B: Named stacks (more flexible)** ```seq : complex ( a b c d e -- ... ) >:first >:second ... :second> :first> ; ``` Named stacks would need scoping rules - probably lexical scope tied to word boundaries. ## The Philosophical Question ### Arguments Against (purity) Concatenative languages derive power from having the stack as the *entire* program state. Every word is `Stack → Stack`. Stash stacks introduce hidden state: ```seq aux> # What does this produce? Depends on invisible state. ``` This is the same problem with variables that concatenative languages avoid. It breaks equational reasoning. ### Arguments For (pragmatism) 1. **Forth precedent** - `>R`/`R>` has existed since the beginning 2. **Still compositional** - `>aux` is `( a -- )`, `aux>` is `( -- a )`, they compose like any word 3. **Scoped/balanced** - No persistent state escapes, unlike `let` bindings 4. **Readability** - Complex shuffles with 5+ items become unreadable with pure combinators ### Comparison to `let` bindings | | `let` binding | Stash stack | |---|---------------|-------------| | Named reference | Yes - `x` refers to value | No - position-based | | Scoped | Yes - lexical scope | No - must balance within word | | Expression | Binds result of expression | Just moves existing value | | Stack effect | Hidden | Explicit | Stash stacks are arguably less of a deviation than `let` because they're still operations, not bindings. ## Open Questions 1. Is the complexity tax worth it, or should complex shuffles signal need for refactoring? 2. Single aux stack vs named stacks? 3. Does this undermine Seq's concatenative identity? 4. What's Seq's position on the purity vs pragmatism spectrum? ## Related - Forth's return stack (`>R`, `R>`, `R@`) - Factor's locals (optional, explicit opt-in) - Joy (stayed purely concatenative) --- *Captured from design discussion. No immediate action needed - for future consideration.*
navicore commented 2026-02-07 15:36:33 +00:00 (Migrated from github.com)
https://github.com/navicore/patch-seq/pull/352
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
navicore/patch-seq#350
No description provided.