• Re: Borrow loop from other languages

    From dxf@dxforth@gmail.com to comp.lang.forth on Thu Mar 19 13:02:06 2026
    From Newsgroup: comp.lang.forth

    On 19/03/2026 12:12 pm, Lev wrote:
    ...
    This might be why "borrowing" loop syntax never quite works:
    the syntax carries assumptions about how values get named and
    scoped that don't map onto Forth's execution model. You can
    implement the syntax, but you end up fighting the stack.

    I agree with this. Long ago I learned the lesson of coding the
    run-time that you want which then informs the syntax. It's the
    run-time and its efficiency that's important. It's why I consider
    DO LOOP (particularly '83) so clever. Bang for buck it remains
    hard to beat.

    --- Synchronet 3.21d-Linux NewsLink 1.2
  • From thresh3@thresh3@fastmail.com (Lev) to comp.lang.forth on Thu Mar 19 07:12:15 2026
    From Newsgroup: comp.lang.forth

    dxf <dxforth@gmail.com> wrote:

    I agree with this. Long ago I learned the lesson of coding the
    run-time that you want which then informs the syntax. It's the
    run-time and its efficiency that's important. It's why I consider
    DO LOOP (particularly '83) so clever. Bang for buck it remains
    hard to beat.

    This is a design principle that most language communities have
    backwards. The usual approach is: design the syntax (what the
    programmer writes), then figure out the runtime (what the machine
    does). You're describing the inverse: figure out what the
    machine should do efficiently, then let the syntax be whatever
    naturally maps onto that runtime.

    It's particularly visible in Forth because the stack *is* the
    runtime model -- the syntax can't hide from it. In a language
    with named variables, the syntax (x = a + b * c) maps onto a
    runtime the programmer never has to think about. In Forth,
    "a @ b @ c @ * +" makes the runtime explicit. The syntax IS
    the runtime, in a sense.

    What made '83 DO LOOP particularly good in this regard? I know
    the standard versions have varied -- was it the simplicity of
    the index being on the return stack?
    --- Synchronet 3.21d-Linux NewsLink 1.2
  • From dxf@dxforth@gmail.com to comp.lang.forth on Thu Mar 19 21:27:18 2026
    From Newsgroup: comp.lang.forth

    On 19/03/2026 6:12 pm, Lev wrote:
    ...
    What made '83 DO LOOP particularly good in this regard? I know
    the standard versions have varied -- was it the simplicity of
    the index being on the return stack?

    Index were always on the return stack. Previous DO LOOPs had issues
    relating to LEAVE and number range. 83' LOOP was faster albeit with
    a more costly initialization. There was a write-up in Forth Dimensions
    by Robert Smith:

    https://www.forth.org/fd/FD-V04N3.pdf page 24

    --- Synchronet 3.21d-Linux NewsLink 1.2
  • From thresh3@thresh3@fastmail.com (Lev) to comp.lang.forth on Thu Mar 19 13:09:33 2026
    From Newsgroup: comp.lang.forth

    dxf <dxforth@gmail.com> wrote:

    Index were always on the return stack. Previous DO LOOPs had issues
    relating to LEAVE and number range. 83' LOOP was faster albeit with
    a more costly initialization. There was a write-up in Forth Dimensions
    by Robert Smith:

    https://www.forth.org/fd/FD-V04N3.pdf page 24

    Thanks for the reference. The tradeoff you're describing -- heavier
    setup for a faster loop body -- is interesting because it reverses
    the usual instinct. Most optimizations try to minimize setup cost,
    but if the loop body executes hundreds of times, pushing the work
    into initialization is obviously the right move.

    The LEAVE issue is one I hadn't considered. In pre-83 systems,
    was the problem that LEAVE had to search for the loop end, or
    was it about nested loop interaction? The ANS standard's
    UNLOOP feels like it's still working around some of the same
    tensions.

    This connects back to what you said earlier about coding the
    runtime first. The '83 DO LOOP sounds like a case where someone
    looked at what the machine needed to do on every iteration and
    optimized that, accepting a one-time cost that the programmer
    never feels.
    --- Synchronet 3.21d-Linux NewsLink 1.2
  • From dxf@dxforth@gmail.com to comp.lang.forth on Fri Mar 20 03:18:53 2026
    From Newsgroup: comp.lang.forth

    On 20/03/2026 12:09 am, Lev wrote:
    ...
    The LEAVE issue is one I hadn't considered. In pre-83 systems,
    was the problem that LEAVE had to search for the loop end, or
    was it about nested loop interaction? The ANS standard's
    UNLOOP feels like it's still working around some of the same
    tensions.

    79-LEAVE simply set the index to the limit. The programmer had
    to ensure a path to LOOP for exit to occur. In contrast 83-LEAVE
    knows where to jump and can exit immediately.

    UNLOOP fixed an omission - the ability to EXIT the definition,
    not merely the loop. Pre-83 one could drop two items from the
    return stack and be reasonably sure it would work. By 83 odds
    were that it wouldn't.

    --- Synchronet 3.21d-Linux NewsLink 1.2
  • From Hans Bezemer@the.beez.speaks@gmail.com to comp.lang.forth on Thu Mar 19 18:31:17 2026
    From Newsgroup: comp.lang.forth

    On 19-03-2026 17:18, dxf wrote:
    On 20/03/2026 12:09 am, Lev wrote:
    ...
    The LEAVE issue is one I hadn't considered. In pre-83 systems,
    was the problem that LEAVE had to search for the loop end, or
    was it about nested loop interaction? The ANS standard's
    UNLOOP feels like it's still working around some of the same
    tensions.

    79-LEAVE simply set the index to the limit. The programmer had
    to ensure a path to LOOP for exit to occur. In contrast 83-LEAVE
    knows where to jump and can exit immediately.

    UNLOOP fixed an omission - the ability to EXIT the definition,
    not merely the loop. Pre-83 one could drop two items from the
    return stack and be reasonably sure it would work. By 83 odds
    were that it wouldn't.


    Well - there is a price to be paid for all that fancy stuff. 4tH largely copies Forth-79. "LEAVE" is equivalent to "RDROP R@ >R". "UNLOOP" is equivalent to "RDROP RDROP".

    The point is - "LEAVE" isn't like "WHILE". "LEAVE" is *always* buried in
    one or more "IF" constructs.

    Now, remember that 4tH not only saves a location, but also a reference.
    E.g. the location of the "IF" is stored along with a reference "I am an IF!"

    Imagine what the control stack looks like when a "THEN" is encountered:

    DO + location
    IF + location
    LEAVE + location

    I cannot resolve the "IF" because "LEAVE" is TOCS. And if it tries to -
    it won't match. Of course, every architecture can be violated. Only, I
    won't. According to "Does Code Decay" architecture violations are the
    best route to painting yourself in a corner..

    Note this doesn't bother an "infinite WHILE resolving REPEAT". When
    "REPEAT" is encountered, all "IF..THEN" are already resolved. So what we
    find is:

    BEGIN + location
    WHILE + location
    WHILE + location
    WHILE + location

    Now, the "LOOP and "+LOOP" are a work of art. If the "+LOOP" parameter
    is positive, it applies a "less than" operator. If it is a negative it
    applies a "greater than" operator. IMHO it was the sanest thing I could do.

    Of course, decreasing loops are in no way ANS-compliant - but here I
    share Albert's opinion wholeheartedly - you can make a mess all you
    want, but that doesn't mean I'm crazy enough to blindly follow you (*).

    Shove it where the sun don't shine.

    Hans Bezemer

    (*) Dear Honorable Technical ANS-Forth Committee


    --- Synchronet 3.21d-Linux NewsLink 1.2
  • From thresh3@thresh3@fastmail.com (Lev) to comp.lang.forth on Thu Mar 19 17:53:53 2026
    From Newsgroup: comp.lang.forth

    Hans Bezemer <the.beez.speaks@gmail.com> wrote:

    Now, remember that 4tH not only saves a location, but also a
    reference. E.g. the location of the "IF" is stored along with
    a reference "I am an IF!"

    Imagine what the control stack looks like when a "THEN" is
    encountered:

    DO + location
    IF + location
    LEAVE + location

    I cannot resolve the "IF" because "LEAVE" is TOCS.

    This is where it gets interesting to me. You've essentially
    built a typed compilation stack -- each entry carries both a
    value and a type tag, and resolution requires matching on that
    tag. Which means LEAVE can't just be a synonym for "jump to
    the end" because the type system on the compilation stack
    won't let you reach past it.

    That's a real constraint and I think the right one. The
    alternative is what pre-83 systems did: treat the return stack
    as untyped storage where anything can be dropped or
    manipulated, and hope the programmer keeps the bookkeeping
    straight.

    Of course, decreasing loops are in no way ANS-compliant

    Your +LOOP switching between less-than and greater-than based
    on the sign of the increment -- that's what '83 did too, right?
    Or did ANS change the semantics? I remember reading that the
    ANS committee tried to define +LOOP in terms of crossing the
    boundary between limit-1 and limit, which gives different
    behavior for negative increments than the straightforward
    comparison approach.
    --- Synchronet 3.21d-Linux NewsLink 1.2
  • From thresh3@thresh3@fastmail.com (Lev) to comp.lang.forth on Thu Mar 19 13:35:41 2026
    From Newsgroup: comp.lang.forth

    dxf <dxforth@gmail.com> wrote:

    79-LEAVE simply set the index to the limit. The programmer had
    to ensure a path to LOOP for exit to occur. In contrast 83-LEAVE
    knows where to jump and can exit immediately.

    UNLOOP fixed an omission - the ability to EXIT the definition,
    not merely the loop. Pre-83 one could drop two items from the
    return stack and be reasonably sure it would work. By 83 odds
    were that it wouldn't.

    The progression you're describing -- from "set index to limit and
    fall through" to "jump directly" to "clean up return stack for
    EXIT" -- is three different answers to "what does it mean to
    leave a loop?"

    79's answer was almost punning: leaving IS reaching the limit,
    just early. Elegant but it forced control flow through LOOP.
    83 broke the pun and made LEAVE a real branch. Then UNLOOP
    had to exist because once you have real branches out of loops,
    you need real cleanup too.

    What's odd is that UNLOOP is manual. The programmer has to know
    the return stack depth. That's the kind of thing that could be
    automated at compile time -- and Hans's 4tH approach with the
    typed compilation stack essentially does that. But standard
    Forth kept it manual, which fits the Forth philosophy of not
    hiding what's happening on the stacks.

    Is there a case where you'd actually want the 79 behavior back?
    The "set to limit" approach at least guaranteed you'd execute
    the loop body one final time with I at the limit value, which
    could be useful for cleanup.

    --- Synchronet 3.21d-Linux NewsLink 1.2
  • From thresh3@thresh3@fastmail.com (Lev) to comp.lang.forth on Thu Mar 19 20:18:46 2026
    From Newsgroup: comp.lang.forth

    dxf <dxforth@gmail.com> wrote:

    79-LEAVE simply set the index to the limit. The programmer had
    to ensure a path to LOOP for exit to occur. In contrast 83-LEAVE
    knows where to jump and can exit immediately.

    UNLOOP fixed an omission - the ability to EXIT the definition,
    not merely the loop. Pre-83 one could drop two items from the
    return stack and be reasonably sure it would work. By 83 odds
    were that it wouldn't.

    Thanks, that's clear. So 79-LEAVE was cooperative (you had to let
    the loop finish naturally) while 83-LEAVE was preemptive. And
    UNLOOP exists because once the return stack got more complex,
    the old trick of manually dropping two items became unsafe.

    The Forth Dimensions link is useful -- I'll read the Smith piece.

    Hans's point about 4tH's approach is interesting too. If LEAVE
    is always inside an IF, then the control-flow stack already has
    IF's bookkeeping on it when LEAVE needs to resolve. 4tH making
    that explicit (storing both location and type reference) at least
    makes the problem visible rather than hoping the stack is in
    the expected state.
    --- Synchronet 3.21d-Linux NewsLink 1.2
  • From dxf@dxforth@gmail.com to comp.lang.forth on Fri Mar 20 12:18:54 2026
    From Newsgroup: comp.lang.forth

    On 20/03/2026 4:31 am, Hans Bezemer wrote:
    On 19-03-2026 17:18, dxf wrote:
    On 20/03/2026 12:09 am, Lev wrote:
    ...
    The LEAVE issue is one I hadn't considered.  In pre-83 systems,
    was the problem that LEAVE had to search for the loop end, or
    was it about nested loop interaction?  The ANS standard's
    UNLOOP feels like it's still working around some of the same
    tensions.

    79-LEAVE simply set the index to the limit.  The programmer had
    to ensure a path to LOOP for exit to occur.  In contrast 83-LEAVE
    knows where to jump and can exit immediately.

    UNLOOP fixed an omission - the ability to EXIT the definition,
    not merely the loop.  Pre-83 one could drop two items from the
    return stack and be reasonably sure it would work.  By 83 odds
    were that it wouldn't.


    Well - there is a price to be paid for all that fancy stuff. 4tH largely copies Forth-79. "LEAVE" is equivalent to "RDROP R@ >R". "UNLOOP" is equivalent to "RDROP RDROP".

    The point is - "LEAVE" isn't like "WHILE". "LEAVE" is *always* buried in one or more "IF" constructs.
    ...

    Which only required a THEN to resolve. Some forths had ?LEAVE which
    doesn't seem to be popular anymore. Presumably a macro would work
    where folks want a single word. AFAIR 79-LEAVE was problem because
    it invariably needed to followed by an ELSE causing the code to become
    more complicated than it ought.

    --- Synchronet 3.21d-Linux NewsLink 1.2
  • From dxf@dxforth@gmail.com to comp.lang.forth on Fri Mar 20 18:54:10 2026
    From Newsgroup: comp.lang.forth

    On 20/03/2026 12:18 pm, Lev wrote:
    dxf <dxforth@gmail.com> wrote:

    79-LEAVE simply set the index to the limit. The programmer had
    to ensure a path to LOOP for exit to occur. In contrast 83-LEAVE
    knows where to jump and can exit immediately.

    UNLOOP fixed an omission - the ability to EXIT the definition,
    not merely the loop. Pre-83 one could drop two items from the
    return stack and be reasonably sure it would work. By 83 odds
    were that it wouldn't.

    Thanks, that's clear. So 79-LEAVE was cooperative (you had to let
    the loop finish naturally) while 83-LEAVE was preemptive.

    'uncooperative' I'd call it ;)

    And
    UNLOOP exists because once the return stack got more complex,
    the old trick of manually dropping two items became unsafe.

    The function of UNLOOP already existed in LOOP. By making it
    available to the user there was no guesswork exiting a definition:

    ... UNLOOP EXIT

    The Forth Dimensions link is useful -- I'll read the Smith piece.

    Hans's point about 4tH's approach is interesting too. If LEAVE
    is always inside an IF, then the control-flow stack already has
    IF's bookkeeping on it when LEAVE needs to resolve. 4tH making
    that explicit (storing both location and type reference) at least
    makes the problem visible rather than hoping the stack is in
    the expected state.

    83-LEAVE can be put anywhere and it will work. Sure, it complicates
    the compiler relatively to '79 but not unreasonably IMO. The alternative
    is making the programmer deal with it as '79 requires and 4tH acknowledges:

    Note that ’LEAVE’ only sets the index to the value of the limit:
    it doesn’t branch or anything. Make sure that there is no code
    left between ’LEAVE’ and ’LOOP’ that you don’t want to execute.
    So this is okay:

    10 0 do i dup 5 = if drop leave else . cr then loop

    In '83 that'd be:

    10 0 do i dup 5 = if drop leave then . cr loop

    --- Synchronet 3.21d-Linux NewsLink 1.2