Digest #215 2023-02-24
Is this, effectively:
: \ REFILL DROP ; IMMEDIATE ?
The description says "... parse and discard...", but wouldn't simply "... discard..." suffice? Also,
REFILL strongly implies that it operates one line at a time. Maybe that is specified elsewhere in the spec?
Programs could use tail-recursion instead of loops.
Concerning the performance advantage of tail-call elimination, there is no need to standardize tail-call elimination for individual systems to reap this benefit. Also, the standard does not specify any performance guarantees.
Backtraces would not contain all calls in a call chain. Forth systems that do not perform tail-call elimination now would have to be rewritten (unless this feature was made optional, but then the advantage would vanish).
Many Forth systems do not perform tail-call elimination.
Several Forth systems, in particular SwiftForth and colorForth perform tail-call elimination, although I don't know of any guarantees. AFAIK colorForth programs are written to perform tail-recursion instead of loops, but I doubt that there is any SwiftForth-specific program that requires this feature (well, maybe some return-address manipulating programs that were written to take the return-stack behaviour of SwiftForth in consideration; but such programs are non-standard either way). Recursion seems to be not very popular among Forth programmers.
I expect that the committee sees things like I discussed them above, so I am closing this comment. If you want consideration from the whole committee, please reopen it.
Guaranteeing tail call elimination would be a disaster. Any word that manipulates the return stack cannot be eliminated. Identifying such words automagically is non-trivial.
: jmp >r ; : (.") r> count 2dup + >r type ;
Now introduce a word further up that adjusts the return stack.
: (.") ((")) type ;
A program that performs return-address manipulation like your examples is not a standard program. So any guarantees the standard might give would not apply to these programs.
Nonetheless, I expect that you have legacy return-address manipulating programs that expect no tail-call elimination. Automatically covering both these legacy programs and hypothetical standard programs that require tail-call elimination would be a challenge (depending on how the guarantee was specified). But it would be possible to cover both with a manual switch.
BTW, the use of return-address manipulation for implementing locals results in VFX 5.11 being slower than lxf by more than a factor of four on sendmore.fth. I understand why you want to support legacy programs that use such techniques, but it's unclear why you still use it internally.
Programs could use tail-recursion instead of loops.
Yes, and it's the only advantage on the source code level.
I employed a kind of loop, which just jumps to the begin of the word. It can be used in any place where
exit can be used, and semantically it's equivalent to tail-recursion
recurse exit. Such a loop can be easily implemented (with some limitations — even in a standard program), and it does not break anything.
A practical result is that in some cases definitions become shorter.
An example: a word that returns the next value (a string) from the current SQL query, having multiple result sets.
: next-value ( -- sd.value | 0 0 ) res if row if next-col? if get-value exit then then next-row? if itself-again then then next-result? if itself-again then 0. ;
NB: the word
next-value is defined in a local namespace, so names are so short.
recurse exit can be used in place of
But it would be possible to cover both with a manual switch.
SP-Forth/4 has such a switch, which turn on tail call elimination. By default it's turned off, since in some cases incorrect code is still produced.
Of course tail call elimination cannot be required by the standard since it's difficult to implement, and sometimes it's even impossible to implement without changing the format of generated code (e.g. in case of generating asmjs).
Standard Forth cannot be implemented to generate asm.js as is, because asm.js does not support run-time code generation (at least that's what a student reported who was interested in a project with that topic). Should we destandardize
COMPILE, etc. because of that? Of course, you can still implement a Forth interpreter in asm.js and such an interpreter can support tail-call elimination.
In any case, hypothetical implementations are a weak reason for non-standardization. If you have a particular asm.js-based implementation in mind, please provide a pointer.
In the long run we should probably specify a (logical) input stack that physically typically (or requiredly?) resides on the return stack, and that
THROW restores, like the other stacks, with appropriate changes in words like
LOAD. The restoration of course has to restore the input source in the same way as regularly leaving
LOAD would. But if the code controlled by
catch just advances the position in the current input stream, no input stack item is pushed and
throw does not restore it, and therefore does not restore the position.
I leave this open so we don't forget about wanting to do this change.
We discussed this question at the meeting on 2023-02-17, and our conclusion was that we do not recommend implementing
RESTORE-INPUT on systems that are so memory constrained that the amount of data-stack memory is a problem.
Now with stack effect comment:
: . ( n -- ) dup abs 0 <# #s rot sign #> type space ;
Thanks for your contribution. We will add it to the document when I get around to my editorial duties.
Closing (the committee does not need to look at it again).
An alternate proposal:
Add the word
DEFINE ( xt c-addr u -- ) as a deferred word through which all words are added to wordlists (e.g., via
SYNONYM, etc.). Calling
DEFINE adds the word specified via ( c-addr u ) to the current wordlist, with execution token xt, and sets it as the most recently-defined word (so that
IMMEDIATE may affect it). While xt need not be a valid execution token, an ambiguous condition would exist if it was not valid and the wordlist in question wound up in the search order. Later, successful calls to
SEARCH-WORDLIST should return the provided xt.
In this manner, the system's own wordlist mechanic can be leveraged to provide the user with arbitrary named storage capability, storing user data as xt. What's more, redefining what
DEFINE is, via
DEFER! (while saving the original somewhere) would allow for the simple creation of facilities like
We might also want a
DELETE-WORDLIST ( wid -- ), to clean up later.
We discussed this issue at the meeting on 2023-02-17. We decided to proceed this proposal to CfV status, in order to hear more feedback from the wider community once the voting actually works.
The issue at hand is: Has the information in your favourite Forth system's documentation about ambiguous conditions really ever been of use to you, or is it just busywork for system implementors?
This issue is superseded by the Proposal to relax the documentation requirements, so we are closing this one.
If any of the participants in the discussion wants to have a discussion of one of the other issues touched in this discussion, you are welcome to open a new topic about that.
You can find my notes here. You can find your chores by searching for "Action: " followed by your shorthand. Let me know when you have finished them, so I can make links to your replies etc. in my notes.