• dynamic scoping in Forth in one line of code

    From Kragen Javier Sitaker@kragen@canonical.org to comp.lang.forth on Fri Aug 28 18:04:58 2026
    From Newsgroup: comp.lang.forth

    A couple of years ago, I noticed that itrCOs possible to implement
    Lisp-style dynamic scoping in standard Forth in one line of code:

    : (let!) dup @ over swap 2r> rot >r rot >r >r >r ! ; : let! (let!) 2r> ! ;

    It turns out the implementation works perfectly (within its limits) in
    all four Forth implementations I could test it in, including F83 from
    01984.

    ItrCOs tricky and not very efficient, though.

    Background on lexical and dynamic scoping -----------------------------------------

    I realized that some readers may not know what rCLdynamic scopingrCY is, so
    I thought I should include a background section for those readers.

    rCLDynamic scopingrCY is a peculiar construct, mostly abandoned in current programming languages, which gives you rCLlocal variablesrCY in an unusual
    way. It was invented accidentally for the first Lisp interpreter in
    01959.

    With dynamic scoping, if a subroutine called, say, rCLMenifeerCY has a local variable named rCLCantosrCY, for example as a named argument, this locality
    is typically implemented as follows:

    1. Upon entry to Menifee, the existing value of Cantos, if any, is saved
    on a stack.
    2. Upon exit from Menifee, the saved value is popped from the stack and
    Cantos is set to it, overwriting any values that may have been
    assigned to Cantos inside Menifee.

    This is closely analogous to how CPU general-purpose registers are saved
    and restored in machine code on subroutine call and return; the
    implementation technique is called rCLshallow bindingrCY.

    For most purposes, this works just like the rCLlexicalrCY kind of local-variable scoping that werCOre used to from languages like C, Rust,
    and PL/1, but if Menifee calls some other subroutine, call it
    rCLYugoslaviarCY, and Yugoslavia reads the variable Cantos, instead of
    seeing the global value of Cantos (if any), it will see MenifeerCOs local value.

    Emacs Lisp is one of the few programming languages that still supports
    dynamic scoping (PostScript being another), and even elisp is starting
    to default to the more efficient and predictable lexical scoping. But,
    given these definitions:

    (defvar Cantos "53" "Example variable for dynamic scoping demo")
    (defun Yugoslavia () (insert Cantos))
    (defun Menifee (Cantos) (Yugoslavia))

    evaluating the Emacs-Lisp form `(Menifee "72")` will insert rCL72rCY into
    your current buffer, not rCL53rCY, because the function parameter Cantos is bound to the argument "72" that is passed using dynamic scoping.

    In a few cases, the dynamic-scoping behavior is more desirable:

    - In Forth, for example, itrCOs fairly common to want to set `base` to
    some value temporarily, so that words like `.` will print out values in
    base 10 or base 16 or whatever you want.

    - In graphics code, itrCOs common to want to set the pen color or current
    font to some value temporarily.

    - In Emacs Lisp, searching commands like `search-forward` are
    case-sensitive or not depending on the value of the Boolean variable
    `case-fold-search`, so if you want to do a case-insensitive search,
    you can establish a local dynamic binding with

    (let ((case-fold-search nil))
    (search-forward ...))

    - In Emacs Lisp, setting the variable `deactivate-mark` to `t`
    deactivates the mark after returning to the main event loop. Various
    kinds of Emacs Lisp functions that modify the buffer implicitly set
    it, so you can establishing a local dynamic bind to *prevent* this
    with

    (let ((deactivate-mark "irrelevant"))
    ...)

    - Common Lisp is statically scoped by default but also supports
    dynamically-scoped rCLspecial variablesrCY, which are useful for things
    like redirecting `*standard-output*` to a string, as
    `with-output-to-string` does:

    * (with-output-to-string (*standard-output*) (prin1 5) (prin1 3))
    "53"

    Van der HorstrCOs `co` operator
    -----------------------------

    In <https://home.hccnet.nl/a.w.m.van.der.horst/forthlecture6.html> Van
    der Horst implements stackless coroutines in Forth by swapping the top
    two items on the return stack, so that its callerrCOs caller will return
    to the point in its caller that called `co`, assuming they arenrCOt using
    the return stack for anything else:

    : co 2r> >r >r ;

    This is very similar in its effect to GolangrCOs `defer`, so it occurred
    to me that you could use it to restore saved variable values.

    Hacking the return stack to implement dynamic scoping in Forth --------------------------------------------------------------

    The ideal user interface would be a word, maybe called something like
    `let!`, which saves the value and location of a variable on the return
    stack, sets the variable to a new value, and arranges for its caller to
    return into value-restoring code when that caller returns. This would
    allow you to, for example, write GForthrCOs `dec.` word as follows:

    decimal : dec. 10 base let! . ;

    The desired stack effect for `let!` in this case is something like

    ( newval a-addr -- ) R: ( -- a-addr oldval let!+m dec.+n )

    We can implement this as follows:

    : (let!) dup @ over swap 2r> rot >r rot >r >r >r ! ; : let! (let!) 2r> ! ;

    This single-line definition does seem to work when I test it in Gforth,
    PForth and PFE. In F83 it requires a definition of `2r>`. After some
    flubs, including crashing DOSBox, this worked:

    : 2r> r> r> r> rot >r swap ;

    ### Walkthrough of the stack effects ###

    This is pretty confusing, and it involves precisely the kind of tricky
    stack manipulation you should really never do, so hererCOs a play-by-play. After `dup @ over`, we have changed the operand stack from

    newval a-addr

    to

    newval a-addr oldval a-addr

    Then with `swap 2r>` we swap oldval and a-addr and get two levels of
    return addresses onto the operand stack:

    newval a-addr a-addr oldval dec.+n let!+m

    Now with `rot >r rot >r` we push oldval and a-addr onto the return
    stack, leaving

    newval a-addr dec.+n let!+m

    Then we push the return addresses back onto the return stack with `>r >r`rCerCorCebut, crucially, they are now in the reverse order, just as when `co` executed `2r> >r >r`. That leaves our operand stack with just

    newval a-addr

    And so, at last, `(let!)` invokes `!` and sets the variable to the
    requested local value.

    And then it returns. But, critically, it doesnrCOt return to `let!`! It returns directly to the callsite where `dec.` or whatever called `let!`, without first executing the `2r> !` after the callsite of `(let!)` in
    `let!`, which I've denoted as `let!+m` in the above. ItrCOs when `dec.`
    (or whatever the caller of `let!` is) returns that we finish executing
    !`, which loads oldval and a-addr onto the operand stack and then
    uses `!` to restore the variable's old value.

    The dynamically-scoped Towers of Hanoi
    --------------------------------------

    variable src variable dest variable stor variable n

    defer move-disc

    : text-disc
    cr ." Move disc " n @ . ." from " src @ emit ." to " dest @ emit ;

    ' text-disc is move-disc

    : hanoi ( src stor dest n -- )
    n let! dest let! stor let! src let!
    n @ 0= if exit then
    src @ dest @ stor @ n @ 1- recurse
    move-disc
    stor @ src @ dest @ n @ 1- recurse ;

    char A char B char C 4 hanoi

    (I used `n @ .` because for some reason PForth doesnrCOt implement `?`.)

    That seems to work just as you would hope in Gforth, PForth, PFE, and
    even F83 (except that F83 requires `ascii` for `char`). IrCOm told it
    even works in Christopher LeonardrCOs <https://github.com/veltas/zenv>, a
    ZX Spectrum Forth! (But only up to 3 discs, because 4 would require 65
    items on the return stack.)

    A blockfile implementation of the above usable with F83 and GForth is at <http://canonical.org/~kragen/sw/dev3/hanoi.blk>.

    Harmony and dissonance with Forth
    ---------------------------------

    The Hanoi example demonstrates that this form of rCLlocal variablesrCY doesnrCOt impede you from factoring out individual lines of code that use
    the variables. Block-scoped lexical local variables would, which has
    often been a justification for not implementing local variables in
    Forths, or for not using themrCerCorCevariables that are lexically subroutine-local would need to be explicitly passed as parameters, while variables shared between the two subroutines can provide implicit
    dataflow.

    Of course, while implicit dataflow can be very flexible, it also makes
    your code harder to debug and understand.

    Reflections on Forth
    --------------------

    This is the kind of thing that gets people excited about Forth: it's
    such a malleable language that you can add fundamental facilities like dynamically-scoped local variables to it in a single line of code.

    Such things can be an attractive nuisance. This implementation isnrCOt
    very efficient; in an interpreted Forth (without superinstructions) it requires, I think, 16 subroutine invocations per local variable. A
    native-code compiled Forth will require something like that number of instructions instead, but your return-address branch predictor will
    explode, so your performance will still go to hell. And such things are
    pretty confusing to debug when they go wrongrCerCorCe`(let!)` has a sequence
    of nine stack manipulations in a row, most of them return-stack
    manipulations.

    And Forth tends to break easily. In Gforth you *can* invoke `let!` from
    the text interpreter usefully:

    3 x let! x ? 3 ok
    x ? 0 ok

    but this behavior is not guaranteed. Running `3 x (let!)` crashes
    Gforth. `Let!` doesnrCOt work inside a `do loop`, either:

    : (let!) dup @ over swap 2r> rot >r rot >r >r >r ! ; : let! (let!) 2r> ! ; ok
    variable x : xsum 0 do x @ i + x let! loop x @ ; ok
    5 xsum .
    :3: Return stack overflow
    5 >>>xsum<<< .
    Backtrace:

    If you wanted to implement dynamic scoping in Forth in an efficient way, yourCOd want to statically allocate space in a stack frame for all the variables a colon definition created local bindings for, copy the old
    values of those variables into that stack frame on entry, and copy them
    out on exit. This would add about two instructions to a call and return sequence for a definition using this facility, plus two instructions per variable. And it would break return-stack manipulations like this one.

    A related problem is that Forth tends to disproportionately attract people
    who like to spend their time hacking on the language implementation
    rather than using it to write code to do something elserCerCorCeboth
    because you *can* hack on the implementation so easily, and because you
    have to understand the implementation in order to debug your failures.
    This can sink projects in Forth.

    *****

    The above is a lightly edited copy of forth-dynamic-scoping.md from <http://canonical.org/~kragen/sw/pavnotes2.git/>. Commentary is eagerly welcomed.

    Kragen
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Paul Rubin@no.email@nospam.invalid to comp.lang.forth on Fri Aug 28 18:12:48 2026
    From Newsgroup: comp.lang.forth

    Kragen Javier Sitaker <kragen@canonical.org> writes:
    A couple of years ago, I noticed that itrCOs possible to implement
    Lisp-style dynamic scoping in standard Forth in one line of code:

    : (let!) dup @ over swap 2r> rot >r rot >r >r >r ! ; : let! (let!) 2r> ! ;

    You need to still restore the old value when the function exits by
    exception or THROW. So you have to use something like CATCH.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From albert@albert@spenarnc.xs4all.nl to comp.lang.forth on Sat Aug 29 12:10:43 2026
    From Newsgroup: comp.lang.forth

    In article <87bjamc5xh.fsf@debian>,
    Kragen Javier Sitaker <kragen@canonical.org> wrote:
    <SNIP>
    Van der HorstrCOs `co` operator
    -----------------------------

    In <https://home.hccnet.nl/a.w.m.van.der.horst/forthlecture6.html> Van
    der Horst implements stackless coroutines in Forth by swapping the top
    two items on the return stack, so that its callerrCOs caller will return
    to the point in its caller that called `co`, assuming they arenrCOt using
    the return stack for anything else:

    : co 2r> >r >r ;

    This is very similar in its effect to GolangrCOs `defer`, so it occurred
    to me that you could use it to restore saved variable values.

    Indeed it is (note the tricky use of the return stack.)
    0 ( H. B. DH. DEC. BASE? HEX: DEC: ) \ AvdH B6dec18
    1 \ Switch to hex for the duration of the definition.
    2 : HEX: R> BASE @ >R >R HEX CO R> BASE ! ;
    3 : DEC: R> BASE @ >R >R DECIMAL CO R> BASE ! ;
    4 ( Add a , after 4 digits )
    5 : 4? 1+ 4 MOD 0= IF &, HOLD THEN ;
    6 : 3? 1+ 3 MOD 0= IF &, HOLD THEN ;
    7 ( Generate string with hex format of DOUBLE of LEN digits)
    8 : (DH.) HEX: <# 1- 0 ?DO # I 4? LOOP # #> ;
    9 : B. S>D 2 (DH.) TYPE ; ( print BYTE in hex )
    10 : H. S>D 2 CELLS (DH.) TYPE ; ( print SINGLE in hex )
    11 : DH. 4 CELLS (DH.) TYPE ; ( print DOUBLE in hex )
    12 ( print DOUBLE in decimal )
    13 : DEC. 5 CELLS DEC: <# 1- 0 ?DO # I 3? LOOP # #> TYPE ;
    14 : BASE? BASE @ B. ; ( print true value of base)

    It is also useful for decorators. A decorator is a debugging
    function executed before each call of a high level function.
    : dec ." BEFORE " .S ;
    ' dec ' myfunction decorated

    Useful as it, is CO enhances this further:
    : dec ." BEFORE " .S CO ." AFTER ".S ;
    Now the decorator prints the stack before and after myfunction
    is executed.

    [ Decorator's are especially useful for Heisenbugs, where you can't
    add inline output to a program where the bug disappears the moment
    you change the addresses of functions. ]

    <SNIP>

    Kragen
    --
    The Chinese government is satisfied with its military superiority over USA.
    The next 5 year plan has as primary goal to advance life expectancy
    over 80 years, like Western Europe.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Kragen Javier Sitaker@kragen@canonical.org to comp.lang.forth on Sat Aug 29 10:50:48 2026
    From Newsgroup: comp.lang.forth

    Paul Rubin <no.email@nospam.invalid> writes:
    Kragen Javier Sitaker <kragen@canonical.org> writes:
    A couple of years ago, I noticed that itrCOs possible to implement
    Lisp-style dynamic scoping in standard Forth in one line of code:

    : (let!) dup @ over swap 2r> rot >r rot >r >r >r ! ; : let! (let!) 2r> ! ;

    You need to still restore the old value when the function exits by
    exception or THROW. So you have to use something like CATCH.

    ThatrCOs an excellent point, and one I hadnrCOt considered. That will definitely make it more than one line. How would you implement that?

    Kragen
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Kragen Javier Sitaker@kragen@canonical.org to comp.lang.forth on Sat Aug 29 11:17:42 2026
    From Newsgroup: comp.lang.forth

    albert@spenarnc.xs4all.nl (Albert van der Horst) writes:
    In article <87bjamc5xh.fsf@debian>,
    Kragen Javier Sitaker <kragen@canonical.org> wrote:
    Van der HorstrCOs `co` operator
    -----------------------------
    (...)
    This is very similar in its effect to GolangrCOs `defer`, so it occurred
    to me that you could use it to restore saved variable values.

    Indeed it is (note the tricky use of the return stack.)
    0 ( H. B. DH. DEC. BASE? HEX: DEC: ) \ AvdH B6dec18
    1 \ Switch to hex for the duration of the definition.
    2 : HEX: R> BASE @ >R >R HEX CO R> BASE ! ;
    3 : DEC: R> BASE @ >R >R DECIMAL CO R> BASE ! ;

    What a pleasant surprise to receive a response from the man himself!

    Yes, you could see the `let!` word as a generalization of your `HEX:`
    and `DEC:`. Are there other standard Forth `variable`s that itrCOs useful
    for?

    5 : 4? 1+ 4 MOD 0= IF &, HOLD THEN ;
    6 : 3? 1+ 3 MOD 0= IF &, HOLD THEN ;

    IrCOm guessing `&,` is how Lina or ciforth spells `[char] ,`?

    13 : DEC. 5 CELLS DEC: <# 1- 0 ?DO # I 3? LOOP # #> TYPE ;

    This is very nice! I hadnrCOt realized comma-separation was quite so
    easy. But I donrCOt fully understand how it works.

    It is also useful for decorators. A decorator is a debugging
    function executed before each call of a high level function.
    : dec ." BEFORE " .S ;
    ' dec ' myfunction decorated

    Useful as it, is CO enhances this further:
    : dec ." BEFORE " .S CO ." AFTER ".S ;
    Now the decorator prints the stack before and after myfunction
    is executed.

    [ Decorators are especially useful for Heisenbugs, where you can't
    add inline output to a program where the bug disappears the moment
    you change the addresses of functions. ]

    In the Lisp world, this is known as rCLadvicerCY or rCLmethod combinationrCY; I IrCOm guessing that `decorated` is specific to Lina or ciforth? I canrCOt
    see how to implement it portably. I suppose the use of `co` here
    depends on `decorated` having already placed the entry point to
    `myfunction` on the return stack before `dec` is invoked?

    I currently have some advice placed on the nntp-send-command function in
    my Emacs because I was trying to figure out why the Gnus newsreader,
    which runs under Emacs, was failing to show me the full list of
    newsfroups. This adds debugging output to the `*trace-output*` buffer
    every time nntp-send-command is called or returns:

    (trace-function-background 'nntp-send-command)

    Kragen
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From albert@albert@spenarnc.xs4all.nl to comp.lang.forth on Sun Aug 30 12:49:23 2026
    From Newsgroup: comp.lang.forth

    In article <874igd9fjt.fsf@debian>,
    Kragen Javier Sitaker <kragen@canonical.org> wrote:
    albert@spenarnc.xs4all.nl (Albert van der Horst) writes:
    In article <87bjamc5xh.fsf@debian>,
    Kragen Javier Sitaker <kragen@canonical.org> wrote:
    Van der HorstrCOs `co` operator
    -----------------------------
    (...)
    This is very similar in its effect to GolangrCOs `defer`, so it occurred >>>to me that you could use it to restore saved variable values.

    Indeed it is (note the tricky use of the return stack.)
    0 ( H. B. DH. DEC. BASE? HEX: DEC: ) \ AvdH B6dec18
    1 \ Switch to hex for the duration of the definition.
    2 : HEX: R> BASE @ >R >R HEX CO R> BASE ! ;
    3 : DEC: R> BASE @ >R >R DECIMAL CO R> BASE ! ;

    What a pleasant surprise to receive a response from the man himself!

    Really? I think CO is a fairly trivial addition and it is present
    in guises in assembler tricks/techniques.


    Yes, you could see the `let!` word as a generalization of your `HEX:`
    and `DEC:`. Are there other standard Forth `variable`s that itrCOs useful >for?

    5 : 4? 1+ 4 MOD 0= IF &, HOLD THEN ;
    6 : 3? 1+ 3 MOD 0= IF &, HOLD THEN ;

    IrCOm guessing `&,` is how Lina or ciforth spells `[char] ,`?
    Indeed. In my own library I hate that ' is overloaded so much.


    13 : DEC. 5 CELLS DEC: <# 1- 0 ?DO # I 3? LOOP # #> TYPE ;

    This is very nice! I hadnrCOt realized comma-separation was quite so
    easy. But I donrCOt fully understand how it works.
    On in three you perform "&, HOLD".


    It is also useful for decorators. A decorator is a debugging
    function executed before each call of a high level function.
    : dec ." BEFORE " .S ;
    ' dec ' myfunction decorated

    Useful as it, is CO enhances this further:
    : dec ." BEFORE " .S CO ." AFTER ".S ;
    Now the decorator prints the stack before and after myfunction
    is executed.

    [ Decorators are especially useful for Heisenbugs, where you can't
    add inline output to a program where the bug disappears the moment
    you change the addresses of functions. ]

    In the Lisp world, this is known as rCLadvicerCY or rCLmethod combinationrCY; I
    IrCOm guessing that `decorated` is specific to Lina or ciforth? I canrCOt

    Decorators are not specific to ciforth, I stole it from Python. I'm
    not aware of other Forths implementing it. Only indirect threaded
    Forth's can implement this easily. Even in ciforth it depends on the
    situation that a ciforth header allows to arrive at the interpreted
    code in two ways.

    see how to implement it portably. I suppose the use of `co` here
    There is no way to implement it portably, and it depends on
    code in the underlying language (assembler or c).
    depends on `decorated` having already placed the entry point to
    `myfunction` on the return stack before `dec` is invoked?
    Indeed. decorated replaces the call to DOCOL in the code field.
    Like DOCOL it stacks the interpreter pointer on the return stack,
    and does something more.

    <SNIP>


    Kragen
    --
    The Chinese government is satisfied with its military superiority over USA.
    The next 5 year plan has as primary goal to advance life expectancy
    over 80 years, like Western Europe.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Kragen Javier Sitaker@kragen@canonical.org to comp.lang.forth on Tue Sep 1 20:38:09 2026
    From Newsgroup: comp.lang.forth

    albert@spenarnc.xs4all.nl writes:
    In article <874igd9fjt.fsf@debian>,
    Kragen Javier Sitaker <kragen@canonical.org> wrote:
    What a pleasant surprise to receive a response from the man himself!

    Really? I think CO is a fairly trivial addition and it is present
    in guises in assembler tricks/techniques.

    I think yourCOve internalized it sufficiently that itrCOs not surprising to you, but it was surprising to me when I encountered it a year and a half
    ago, at which point IrCOd been programming for 43 years and occasionally programming in Forth for 27 years. Maybe I'm just a slow learner.

    Kragen
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Paul Rubin@no.email@nospam.invalid to comp.lang.forth on Tue Sep 1 17:15:12 2026
    From Newsgroup: comp.lang.forth

    Kragen Javier Sitaker <kragen@canonical.org> writes:
    I think yourCOve internalized it sufficiently that itrCOs not surprising to you, but it was surprising to me when I encountered it a year and a half
    ago, at which point IrCOd been programming for 43 years and occasionally programming in Forth for 27 years. Maybe I'm just a slow learner.

    The idea has been in the literature for a long time (it's discussed at
    length in Knuth vol. 1 from 1968), but I think it wasn't well understood
    in the programming community til much more recently. This might be of interest:

    https://www.chiark.greenend.org.uk/~sgtatham/quasiblog/coroutines-philosophy/

    CO is similar to Knuth's (TAOCP's) coroutine jump as explained in the "Coroutine paradigms" section of the article, I think.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Kragen Javier Sitaker@kragen@canonical.org to comp.lang.forth on Thu Sep 3 20:38:30 2026
    From Newsgroup: comp.lang.forth

    Paul Rubin <no.email@nospam.invalid> writes:
    Kragen Javier Sitaker <kragen@canonical.org> writes:
    I think yourCOve internalized it sufficiently that itrCOs not surprising to >> you, but it was surprising to me when I encountered it a year and a half
    ago, at which point IrCOd been programming for 43 years and occasionally
    programming in Forth for 27 years. Maybe I'm just a slow learner.

    The idea has been in the literature for a long time (it's discussed at
    length in Knuth vol. 1 from 1968),

    No, no, I know what coroutines are; I learned about them last millennium
    when I read Knuth vol. 1. My copy has pencil annotations in the margin expressing my skepticism; but, since then IrCOve used Icon generators,
    Python generators, Python asyncio, Prolog backtracking, and Lua rCLcoroutines,rCY which might more precisely be called rCLthreadsrCY.

    What surprised me was the extremely parsimonious mechanism of Van der
    HorstrCOs `co`.

    Kragen
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From dxf@dxforth@gmail.com to comp.lang.forth on Fri Sep 4 15:24:31 2026
    From Newsgroup: comp.lang.forth

    On 4/09/2026 9:38 am, Kragen Javier Sitaker wrote:
    Paul Rubin <no.email@nospam.invalid> writes:
    Kragen Javier Sitaker <kragen@canonical.org> writes:
    I think yourCOve internalized it sufficiently that itrCOs not surprising to >>> you, but it was surprising to me when I encountered it a year and a half >>> ago, at which point IrCOd been programming for 43 years and occasionally >>> programming in Forth for 27 years. Maybe I'm just a slow learner.

    The idea has been in the literature for a long time (it's discussed at
    length in Knuth vol. 1 from 1968),

    No, no, I know what coroutines are; I learned about them last millennium
    when I read Knuth vol. 1. My copy has pencil annotations in the margin expressing my skepticism; but, since then IrCOve used Icon generators,
    Python generators, Python asyncio, Prolog backtracking, and Lua rCLcoroutines,rCY which might more precisely be called rCLthreadsrCY.

    What surprised me was the extremely parsimonious mechanism of Van der HorstrCOs `co`.

    I'm more pragmatist, so if it solves an existing problem then I'm interested. For years I knew how to 'localize' variables. But it was all done manually. When Albert and Hans showed me how to implement it simply and with auto-restore,
    it made my day.

    : ;: >r ;

    : LOCAL ( x adr -- ) r> -rot dup @ over 2>r ! ;: 2r> ! ;

    \\ Example
    variable A variable B 8 a ! 7 b !

    : divide ( a b -- )
    b local a local a @ b @ / . cr ;

    15 3 divide a ? b ?

    I use it where re-entrancy is required. While I have loadable 'real' locals,
    I hardy bother as this is simpler.

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Kragen Javier Sitaker@kragen@canonical.org to comp.lang.forth on Fri Sep 4 13:04:10 2026
    From Newsgroup: comp.lang.forth

    dxf <dxforth@gmail.com> writes:
    I'm more pragmatist, so if it solves an existing problem then I'm interested. For years I knew how to 'localize' variables. But it was all done manually. When Albert and Hans showed me how to implement it simply and with auto-restore,
    it made my day.

    : ;: >r ;

    : LOCAL ( x adr -- ) r> -rot dup @ over 2>r ! ;: 2r> ! ;

    \\ Example
    variable A variable B 8 a ! 7 b !

    : divide ( a b -- )
    b local a local a @ b @ / . cr ;

    15 3 divide a ? b ?

    I use it where re-entrancy is required. While I have loadable 'real' locals, I hardy bother as this is simpler.

    This seems to provide exactly the same dynamic-scoping functionality as
    the `let!` I posted, but with a simpler implementation!

    How would you extend it to support `throw`?

    Kragen
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From dxf@dxforth@gmail.com to comp.lang.forth on Sat Sep 5 02:59:32 2026
    From Newsgroup: comp.lang.forth

    On 5/09/2026 2:04 am, Kragen Javier Sitaker wrote:
    dxf <dxforth@gmail.com> writes:
    I'm more pragmatist, so if it solves an existing problem then I'm interested.
    For years I knew how to 'localize' variables. But it was all done manually. >> When Albert and Hans showed me how to implement it simply and with auto-restore,
    it made my day.

    : ;: >r ;

    : LOCAL ( x adr -- ) r> -rot dup @ over 2>r ! ;: 2r> ! ;

    \\ Example
    variable A variable B 8 a ! 7 b !

    : divide ( a b -- )
    b local a local a @ b @ / . cr ;

    15 3 divide a ? b ?

    I use it where re-entrancy is required. While I have loadable 'real' locals,
    I hardy bother as this is simpler.

    This seems to provide exactly the same dynamic-scoping functionality as
    the `let!` I posted, but with a simpler implementation!

    How would you extend it to support `throw`?

    I've yet to need it but pushing locals requiring preserving to the
    return stack before the CATCH and popping them after should do it.
    It's additional overhead so only on a case by case basis.

    --- Synchronet 3.22a-Linux NewsLink 1.2