I don't agree with either of you.
HAA says: "Removing choice is of course what standards do." (true)
And he says: "Standards are an ever narrowing jail that ultimately chokes on itself." (not true).
A standard does remove choice, obviously, because the programmer is restrained by the rules (there should be rules; not ambiguity like ANS-Forth has). There is no reason for the rules to be "ever narrowing" though --- you adopt your standard and then you stick with it --- also, the point of the standard is portability of software (mostly code-libraries), so if people want to have code-libraries available then they voluntarily accept the rules (it is not a jail).
E.R. says: "The advent of ANS Forth liberated Forth system developers from the constraints of implementation specifications in Forth83, making possible the era of optimizing compilers and other advances that have led to
faster, more powerful systems."
This lie made Jeff Fox so mad! I would say that Elizabeth Rather falsely taking credit for making Forth optimizing compilers possible, was a major contributor to his death.
ANS-Forth is such a screwed up standard that any significant work in ANS-Forth is primarily involved in working around the idiotic problems of ANS-Forth, rather than solving the problem itself. For example, my early-binding MACRO: was written in UR/Forth initially and was Forth-83 compliant (almost all of MFX was Forth-83 compliant). In ANS-Forth my early-binding MACRO: had to wait until I had disambiguifiers available because FIND is broken in ANS-Forth. This is why it didn't get written until 2016. I knew about the disambiguifiers in 2010 and could have written the early-binding MACRO: then, but this had to wait until I wrote STR>NUM which I did in 2016. Even something trivial such as an early-binding MACRO:, which was easy in Forth-83, is difficult in ANS-Forth.
My MACRO: (both the late-binding and early-binding versions) do inlining. Inlining is a pretty easy optimization, and this was commonly done in Forth-83. For the most part, inlining is the only optimization being done in SwiftForth today.
: init-list 0 over ! ; ok
see over
405C5F 4 # EBP SUB 83ED04
405C62 EBX 0 [EBP] MOV 895D00
405C65 4 [EBP] EBX MOV 8B5D04
405C68 RET C3 ok
see !
40572F 0 [EBP] EAX MOV 8B4500
405732 EAX 0 [EBX] MOV 8903
405734 4 [EBP] EBX MOV 8B5D04
405737 8 # EBP ADD 83C508
40573A RET C3 ok
Notice how OVER and ! are just being pasted in unchanged:
see init-list
478F0F 4 # EBP SUB 83ED04
478F12 EBX 0 [EBP] MOV 895D00
478F15 0 # EBX MOV BB00000000 \ 0 ends here
478F1A 4 # EBP SUB 83ED04
478F1D EBX 0 [EBP] MOV 895D00
478F20 4 [EBP] EBX MOV 8B5D04 \ OVER ends here
478F23 0 [EBP] EAX MOV 8B4500
478F26 EAX 0 [EBX] MOV 8903
478F28 4 [EBP] EBX MOV 8B5D04
478F2B 8 # EBP ADD 83C508 \ ! ends here
478F2E RET C3 ok
So, how exactly did ANS-Forth make optimizing compilers possible? This is the same kind of optimization that was done in Forth-83. Also, this isn't really optimization --- all it does is get rid of the CALL/RET for each sub-function, but this has very little effect because the modern x86 does CALL/RET prediction anyway --- SwiftForth is still moving data on and off the data-stack needlessly, and all of this memory access is the major speed killer (optimization involves holding data in registers).
This is VFX:
see init-list
INIT-LIST
( 004E1030 C70300000000 ) MOV DWord Ptr 0 [EBX], 00000000
( 004E1036 C3 ) NEXT,
( 7 bytes, 2 instructions )
ok
For the most part, the point of having a standard is to support code-libraries. Elizabeth Rather has fought tooth and nail against code-libraries her entire career though. She refers to the call for code-libraries to be an "endless whine" --- that is grossly offensive! --- that is also abysmally ignorant. This is the primary reason why Forth has become obscure --- nobody is going to use a language that doesn't support code-libraries.
Straight Forth doesn't support parallelization, so Oforth would be a better choice for programs that need this. Oforth and Factor don't support cross-compilers though, so Straight Forth would be a better choice for that. Other than cross-compilers, Straight Forth is mostly for numeric programs --- Straight Forth does have a string-stack though, so it would be better than ANS-Forth (or C) for text handling.
Straight Forth has quotations, which C lacks, so Straight Forth is primarily competing against C --- competing against ANS-Forth is nonsense, because nobody uses ANS-Forth --- Straight Forth is not competing against all of the Scheme/Lisp derived languages such as Ruby, Python, Factor, etc. which are widely used for script-writing and web-servers.
I'm not saying that Straight Forth is the end-all of Forth. I'm just saying that Straight Forth would support code-libraries, so it would be an actual Standard, rather than just another toy Forth system like ANS-Forth or JonesForth. Straight Forth has quotations using the fat-xt method, so it supports general-purpose data-structures. Here is a snippet from my StraightForth.txt document (note that PTR and CNT are user-registers):
---------------------------------------------------------------------------
subsection 1.3.3. --- HOFs for chains
=====================================
A "chain" is a data-structure that is ordered by a key which is a string. A chain can be used as an association, which is like an array indexed by strings, although it is not going to be as fast as a hash-table (if this is a problem, I may implement hash-tables at some time in the future, but Straight Forth isn't competing against Lua so this may never be an issue). A chain is ordered, and it is expected that the key will typically be the string representation of a number (the strings should all be the same length and have the decimal point in the same position, so they compare the same as numbers would compare). In a cross-compiler, for example, the keys would be line-numbers --- also, numeric programs may use chains for sparse arrays.
In a chain, a pointer to a node indicates that this is the current node that we are interested in. This is not necessarily the head (left-most) or tail (right-most) in the chain.
|FULL-RITE ( xt -- ) needs PTR = current-node
Execute xt for each node in the entire list starting at the head-node and moving right.
When the xt function executes, PTR points to the current node.
|FULL-LEFT ( xt -- ) needs PTR = current-node
Execute xt for each node in the entire list starting at the tail-node and moving left.
When the xt function executes, PTR points to the current node.
|EACH-RITE ( xt -- ) needs PTR = current-node
Execute xt for each node starting at PTR@ and moving right.
When the xt function executes, PTR points to the current node.
|EACH-LEFT ( xt -- ) needs PTR = current-node
Execute xt for each node starting at PTR@ and moving left.
When the xt function executes, PTR points to the current node.
|FULL-RITE-UNTIL ( xt -- false | node true ) needs PTR = current-node --- sets PTR= node right of found-node
Execute xt for each node in the entire list starting at the head-node and moving right.
When the xt function executes, PTR points to the current node.
The xt function must return a flag. If the FLAG is TRUE then early-exit.
Return the node that caused the early-exit if there was an early-exit.
PTR is set to the node right of found-node (NIL if found-node was the right-most node).
PTR is restored if there was no early-exit.
|FULL-LEFT-UNTIL ( xt -- false | node true ) needs PTR = current-node --- sets PTR= node left of found-node
Execute xt for each node in the entire list starting at the tail-node and moving left.
When the xt function executes, PTR points to the current node.
The xt function must return a flag. If the FLAG is TRUE then early-exit.
Return the node that caused the early-exit if there was an early-exit.
PTR is set to the node left of found-node (NIL if found-node was the left-most node).
PTR is restored if there was no early-exit.
|EACH-RITE-UNTIL ( xt -- false | node true ) needs PTR = current-node --- sets PTR= node right of found-node
Execute xt for each node starting at PTR@ and moving right.
When the xt function executes, PTR points to the current node.
The xt function must return a flag. If the FLAG is TRUE then early-exit.
Return the node that caused the early-exit if there was an early-exit.
PTR is set to the node right of found-node (NIL if found-node was the right-most node).
PTR is restored if there was no early-exit.
|EACH-LEFT-UNTIL ( xt -- false | node true ) needs PTR = current-node --- sets PTR= node left of found-node
Execute xt for each node starting at PTR@ and moving left.
When the xt function executes, PTR points to the current node.
The xt function must return a flag. If the FLAG is TRUE then early-exit.
Return the node that caused the early-exit if there was an early-exit.
PTR is set to the node left of found-node (NIL if found-node was the left-most node).
PTR is restored if there was no early-exit.
The following aren't HOFs, but are just some functions for chains:
LEFT-LENGTH ( -- n ) needs PTR = current-node
Returns the number of nodes to the left of PTR@.
RITE-LENGTH ( -- n ) needs PTR = current-node
Returns the number of nodes to the right of PTR@.
LENGTH ( -- n ) needs PTR = current-node
Returns the total number of nodes in the chain.
HEAD ( -- flag ) needs PTR = current-node --- sets PTR= head-node
Returns TRUE if PTR@ was already the head-node, or FALSE if PTR was changed.
TAIL ( -- flag ) needs PTR = current-node --- sets PTR= tail-node
Returns TRUE if PTR@ was already the tail-node, or FALSE if PTR was changed.
LEFT ( -- flag ) needs PTR = current-node --- sets PTR= left-of-current-node
Returns TRUE if PTR changed, or FALSE if PTR@ was already the left-most node.
PTR is set to the node left of the current-node if possible.
PTR is restored if it is already the left-most node.
RITE ( -- flag ) needs PTR = current-node --- sets PTR= rite-of-current-node
Returns TRUE if PTR changed, or FALSE if PTR@ was already the right-most node.
PTR is set to the node right of the current-node if possible.
PTR is restored if it is already the right-most node.
INSERT-WEAK ( chain -- clash-chain ) needs PTR = current-node
Abort if CHAIN is already in the current chain.
Insert CHAIN into the current chain.
If any of CHAIN's nodes clash (have the same key as a node in the current chain),
then no insertion is done and the clashing node is inserted into CLASH-CHAIN instead.
INSERT-STRONG ( chain -- clash-chain ) needs PTR = current-node
Abort if CHAIN is already in the current chain.
Insert CHAIN into the current chain.
If any of CHAIN's nodes clash (have the same key as a node in the current chain),
then the insertion is done, but first the old node is removed and inserted into CLASH-CHAIN.
It is illegal to have multiple nodes in a chain with the same key. INSERT-WEAK and INSERT-STRONG remove the nodes with duplicate keys rather than mix them together in the same chain, and these duplicates are all put in a new clash-chain. Normally the clash-chain should be empty (returned as a NIL node) --- if any nodes get put in the clash-chain, this usually means that there is a bug in the program.
SAFE-REMOVE ( node -- ) needs PTR = current-node --- sets PTR= head-node
Remove NODE from the list.
Set PTR to the head-node (not necessarily the same as the previous head-node).
REMOVE ( node -- ) needs PTR = current-node
Abort if NODE = PTR@.
Remove NODE from the list.
Removing a node is problematic because you have to be careful not to remove the only node in the chain that you have a pointer for. SAFE-REMOVE solves this problem by setting PTR to a valid node, but you lose track of which node is your current-node. If you are sure that your current-node is not the node that you are removing (you usually are) then REMOVE lets you keep your current-node in PTR.
DELINK-LEFT ( -- left-chain ) needs PTR = current-node
Split the chain to the left of PTR@ so PTR@ becomes the head of the current chain.
Return LEFT-CHAIN as the tail of a new chain.
DELINK-RITE ( -- rite-chain ) needs PTR = current-node
Split the chain to the right of PTR@ so PTR@ becomes the tail of the current chain.
Return RITE-CHAIN as the head of a new chain.
SEARCH ( -- false | node true ) s( str -- ) needs PTR = current-node
Search the current chain for a node with STR as its key.
Return the found node, or FALSE if nothing was found.
OPTIMIZE-SEARCH ( -- ) needs PTR = current-node
Make the chain more efficient for future calls to SEARCH.
The programmer shouldn't bother with this unless he has reason to believe that it will improve his program's speed.
Overuse of this may degrade a program's speed, so it should be used judiciously
Straight Forth standardizes behavior, not implementation, so the programmer shouldn't rely on any assumptions about implementation. Just to satisfy curiosity though, I will mention that I'm implementing chains as AVL trees. Each node has three pointers, which are left-down, rite-down and up. The up-pointer is needed so LEFT and RITE will work, as PTR does not necessarily point to the root node. Each node also keeps track of its weight which is used by the AVL self-balancing algorithm. OPTIMIZE-SEARCH doesn't do anything at all. I used the name "chain" rather than "tree" (my original name) because binary-trees aren't the only reasonable way that chains could be implemented --- doubly linked lists would also work and would save some memory --- different implementations of Straight Forth may have different implementations of chains.
---------------------------------------------------------------------------