Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.forth > #135467 > unrolled thread

dynamic scoping in Forth in one line of code

Started byKragen Javier Sitaker <kragen@canonical.org>
First post2026-08-28 18:04 -0300
Last post2026-09-05 02:59 +1000
Articles 12 — 4 participants

Back to article view | Back to comp.lang.forth


Contents

  dynamic scoping in Forth in one line of code Kragen Javier Sitaker <kragen@canonical.org> - 2026-08-28 18:04 -0300
    Re: dynamic scoping in Forth in one line of code Paul Rubin <no.email@nospam.invalid> - 2026-08-28 18:12 -0700
      Re: dynamic scoping in Forth in one line of code Kragen Javier Sitaker <kragen@canonical.org> - 2026-08-29 10:50 -0300
    Re: dynamic scoping in Forth in one line of code albert@spenarnc.xs4all.nl - 2026-08-29 12:10 +0200
      Re: dynamic scoping in Forth in one line of code Kragen Javier Sitaker <kragen@canonical.org> - 2026-08-29 11:17 -0300
        Re: dynamic scoping in Forth in one line of code albert@spenarnc.xs4all.nl - 2026-08-30 12:49 +0200
          Re: dynamic scoping in Forth in one line of code Kragen Javier Sitaker <kragen@canonical.org> - 2026-09-01 20:38 -0300
            Re: dynamic scoping in Forth in one line of code Paul Rubin <no.email@nospam.invalid> - 2026-09-01 17:15 -0700
              coroutines (was Re: dynamic scoping in Forth in one line of code) Kragen Javier Sitaker <kragen@canonical.org> - 2026-09-03 20:38 -0300
                Re: coroutines (was Re: dynamic scoping in Forth in one line of code) dxf <dxforth@gmail.com> - 2026-09-04 15:24 +1000
                  dynamic scoping in Forth in one line of code (was Re: coroutines) Kragen Javier Sitaker <kragen@canonical.org> - 2026-09-04 13:04 -0300
                    Re: dynamic scoping in Forth in one line of code (was Re: coroutines) dxf <dxforth@gmail.com> - 2026-09-05 02:59 +1000

#135467 — dynamic scoping in Forth in one line of code

FromKragen Javier Sitaker <kragen@canonical.org>
Date2026-08-28 18:04 -0300
Subjectdynamic scoping in Forth in one line of code
Message-ID<87bjamc5xh.fsf@debian>
A couple of years ago, I noticed that it’s 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.

It’s tricky and not very efficient, though.

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

I realized that some readers may not know what “dynamic scoping” is, so
I thought I should include a background section for those readers.

“Dynamic scoping” is a peculiar construct, mostly abandoned in current
programming languages, which gives you “local variables” in an unusual
way.  It was invented accidentally for the first Lisp interpreter in
01959.

With dynamic scoping, if a subroutine called, say, “Menifee” has a local
variable named “Cantos”, 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 “shallow binding”.

For most purposes, this works just like the “lexical” kind of
local-variable scoping that we’re used to from languages like C, Rust,
and PL/1, but if Menifee calls some other subroutine, call it
“Yugoslavia”, and Yugoslavia reads the variable Cantos, instead of
seeing the global value of Cantos (if any), it will see Menifee’s 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 “72” into
your current buffer, not “53”, 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, it’s 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, it’s 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 “special variables”, 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 Horst’s `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 caller’s caller will return
to the point in its caller that called `co`, assuming they aren’t using
the return stack for anything else:

    : co  2r> >r >r ;

This is very similar in its effect to Golang’s `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 GForth’s `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 here’s 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` — but, 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 doesn’t 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.  It’s when `dec.`
(or whatever the caller of `let!` is) returns that we finish executing
`2r> !`, 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 doesn’t implement `?`.)

That seems to work just as you would hope in Gforth, PForth, PFE, and
even F83 (except that F83 requires `ascii` for `char`).  I’m told it
even works in Christopher Leonard’s <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 “local variables”
doesn’t 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 them — variables 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 isn’t
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 wrong — `(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!` doesn’t 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,
you’d 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 else — both
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

[toc] | [next] | [standalone]


#135469

FromPaul Rubin <no.email@nospam.invalid>
Date2026-08-28 18:12 -0700
Message-ID<87y0dplofj.fsf@nightsong.com>
In reply to#135467
Kragen Javier Sitaker <kragen@canonical.org> writes:
> A couple of years ago, I noticed that it’s 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.

[toc] | [prev] | [next] | [standalone]


#135476

FromKragen Javier Sitaker <kragen@canonical.org>
Date2026-08-29 10:50 -0300
Message-ID<87fqzx9gsn.fsf@debian>
In reply to#135469
Paul Rubin <no.email@nospam.invalid> writes:
> Kragen Javier Sitaker <kragen@canonical.org> writes:
>> A couple of years ago, I noticed that it’s 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.

That’s an excellent point, and one I hadn’t considered.  That will
definitely make it more than one line.  How would you implement that?

Kragen

[toc] | [prev] | [next] | [standalone]


#135471

Fromalbert@spenarnc.xs4all.nl
Date2026-08-29 12:10 +0200
Message-ID<nnd$4aef30ef$417a695d@6608074c8df9df23>
In reply to#135467
In article <87bjamc5xh.fsf@debian>,
Kragen Javier Sitaker  <kragen@canonical.org> wrote:
<SNIP>
>Van der Horst’s `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 caller’s caller will return
>to the point in its caller that called `co`, assuming they aren’t using
>the return stack for anything else:
>
>    : co  2r> >r >r ;
>
>This is very similar in its effect to Golang’s `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.

[toc] | [prev] | [next] | [standalone]


#135478

FromKragen Javier Sitaker <kragen@canonical.org>
Date2026-08-29 11:17 -0300
Message-ID<874igd9fjt.fsf@debian>
In reply to#135471
albert@spenarnc.xs4all.nl (Albert van der Horst) writes:
> In article <87bjamc5xh.fsf@debian>,
> Kragen Javier Sitaker  <kragen@canonical.org> wrote:
>>Van der Horst’s `co` operator
>>-----------------------------
>>(...)
>>This is very similar in its effect to Golang’s `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 it’s useful
for?

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

I’m 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 hadn’t realized comma-separation was quite so
easy.  But I don’t 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 “advice” or “method combination”; I
I’m guessing that `decorated` is specific to Lina or ciforth?  I can’t
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

[toc] | [prev] | [next] | [standalone]


#135491

Fromalbert@spenarnc.xs4all.nl
Date2026-08-30 12:49 +0200
Message-ID<nnd$5e90e337$543815e0@6eeb63a0693c71c3>
In reply to#135478
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 Horst’s `co` operator
>>>-----------------------------
>>>(...)
>>>This is very similar in its effect to Golang’s `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 it’s useful
>for?
>
>>  5  : 4?  1+ 4 MOD 0= IF &, HOLD THEN ;
>>  6  : 3?  1+ 3 MOD 0= IF &, HOLD THEN ;
>
>I’m 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 hadn’t realized comma-separation was quite so
>easy.  But I don’t 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 “advice” or “method combination”; I
>I’m guessing that `decorated` is specific to Lina or ciforth?  I can’t

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.

[toc] | [prev] | [next] | [standalone]


#135517

FromKragen Javier Sitaker <kragen@canonical.org>
Date2026-09-01 20:38 -0300
Message-ID<875x0oa6fy.fsf@debian>
In reply to#135491
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 you’ve internalized it sufficiently that it’s not surprising to
you, but it was surprising to me when I encountered it a year and a half
ago, at which point I’d been programming for 43 years and occasionally
programming in Forth for 27 years.  Maybe I'm just a slow learner.

Kragen

[toc] | [prev] | [next] | [standalone]


#135518

FromPaul Rubin <no.email@nospam.invalid>
Date2026-09-01 17:15 -0700
Message-ID<87ecfcld9r.fsf@nightsong.com>
In reply to#135517
Kragen Javier Sitaker <kragen@canonical.org> writes:
> I think you’ve internalized it sufficiently that it’s not surprising to
> you, but it was surprising to me when I encountered it a year and a half
> ago, at which point I’d 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.

[toc] | [prev] | [next] | [standalone]


#135546 — coroutines (was Re: dynamic scoping in Forth in one line of code)

FromKragen Javier Sitaker <kragen@canonical.org>
Date2026-09-03 20:38 -0300
Subjectcoroutines (was Re: dynamic scoping in Forth in one line of code)
Message-ID<87mrtxzz0p.fsf_-_@debian>
In reply to#135518
Paul Rubin <no.email@nospam.invalid> writes:
> Kragen Javier Sitaker <kragen@canonical.org> writes:
>> I think you’ve internalized it sufficiently that it’s not surprising to
>> you, but it was surprising to me when I encountered it a year and a half
>> ago, at which point I’d 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 I’ve used Icon generators,
Python generators, Python asyncio, Prolog backtracking, and Lua
“coroutines,” which might more precisely be called “threads”.

What surprised me was the extremely parsimonious mechanism of Van der
Horst’s `co`.

Kragen

[toc] | [prev] | [next] | [standalone]


#135548 — Re: coroutines (was Re: dynamic scoping in Forth in one line of code)

Fromdxf <dxforth@gmail.com>
Date2026-09-04 15:24 +1000
SubjectRe: coroutines (was Re: dynamic scoping in Forth in one line of code)
Message-ID<6a9a560f$1@news.ausics.net>
In reply to#135546
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 you’ve internalized it sufficiently that it’s not surprising to
>>> you, but it was surprising to me when I encountered it a year and a half
>>> ago, at which point I’d 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 I’ve used Icon generators,
> Python generators, Python asyncio, Prolog backtracking, and Lua
> “coroutines,” which might more precisely be called “threads”.
> 
> What surprised me was the extremely parsimonious mechanism of Van der
> Horst’s `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.

[toc] | [prev] | [next] | [standalone]


#135555 — dynamic scoping in Forth in one line of code (was Re: coroutines)

FromKragen Javier Sitaker <kragen@canonical.org>
Date2026-09-04 13:04 -0300
Subjectdynamic scoping in Forth in one line of code (was Re: coroutines)
Message-ID<87ik4l6m11.fsf_-_@debian>
In reply to#135548
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

[toc] | [prev] | [next] | [standalone]


#135557 — Re: dynamic scoping in Forth in one line of code (was Re: coroutines)

Fromdxf <dxforth@gmail.com>
Date2026-09-05 02:59 +1000
SubjectRe: dynamic scoping in Forth in one line of code (was Re: coroutines)
Message-ID<6a9af8f3$1@news.ausics.net>
In reply to#135555
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.

[toc] | [prev] | [standalone]


Back to top | Article view | comp.lang.forth


csiph-web