Here is the old argument for LOOP-UNTIL and LOOP-WHILE from the Trello, for historical reference.
The points are still pretty reasonable, and I don't think it was any kind of disaster. It just didn't move the needle very much...some things looked better, some things less good, it was near break-even. And with WHILE-NOT and UNTIL-NOT I think the mechanical arguments for having all 4 variants were covered.
There are simply bigger fish to fry than this particular change.
Historically Rebol has had a WHILE construct that takes two arguments...a condition block and body block:
x: 0
while [x < 3] [
x: x + 1
]
The condition is evaluated first, before the body is ever run. Then alternately the body is evaluated and the condition re-evaluated until the condition comes back conditionally false (e.g. BLANK!, FALSE). At that point, the return result of the WHILE expression is the last evaluation product of the body. (3 in the case above.)
Under the hood it "takes turns" ramping up the evaluator twice each loop cycle on two different blocks. In a sense this can be seen as making the loop itself somewhat less efficient than if it had been chosen to be single-arity, and merely testing the result of a single block evaluation. Ren/C adds this more efficient construct as LOOP-WHILE:
x: 0
loop-while [
x: x + 1
x < 3
]
Using the prefix LOOP- is abstractly filling in the directive of "make a loop" or "turn a loop", to help inform what to do in lieu of any supplied body (the usual expectation being for while loops to have one). Other choices might be noop-while
, continue-while
, cycle-while
etc. But LOOP- seemed the best prefix.
(Note: It is unrelated to what the command or dialect LOOP winds up meaning.)
The relative efficiency of LOOP-WHILE might lead one to ask if it deserves the WHILE name and should replace it entirely. There are several arguments for keeping the two separate constructs, plus keeping WHILE as-is:
- LOOP-WHILE forces the test after the "body code"...so the "body" will run once before the test, which you may or may not want
- LOOP-WHILE cannot return the last result of evaluating the "body" (which is usually more interesting than returning the result of evaluating a condition for a while, given the only possibilities for that are FALSE or BLANK!).
- WHILE is ubiquitously used in arity-2 form in existing code...by nearly every person to ever use the Rebol language.
- WHILE's visual separation of the condition from the body with separate blocks serves a communicative purpose which helps drive its popularity.
It's also worth noting that LOOP-WHILE may be more efficient when standing alone. But it's not more efficient if (for instance) you are dealing with condition and body blocks that aren't already in independent variables. The following will be measurably slower than just while condition body
(and may behave differently, and return a different result!):
x: 0
condition: [x < 3]
body: [x: x + 1]
loop-while [
do body
do condition
]
This understanding of WHILE informs Ren/C's accompanying introduction of an arity-2 UNTIL to match:
x: 0
until [x = 3] [
x: x + 1
]
It runs the condition before the first execution of the body, and returns the last evaluative result of the body before the condition became true.
There is also a LOOP-UNTIL:
x: 0
loop-until [
x: x + 1
x = 3
]
This makes LOOP-UNTIL a synonym for what Rebol2 and R3-Alpha just called UNTIL:
x: 0
until [
x: x + 1
x = 3
]
The reasons for retaking the name UNTIL are:
- every new "non-guru" user (and many of the other "guru" users) found Rebol2's UNTIL to be an inconsistency of the words WHILE and UNTIL. They suggest a matched pair like IF and UNLESS.
- A two-arity UNTIL is genuinely useful in its own right, for the same reasons that WHILE is, including bringing more efficiency to some scenarios than LOOP-UNTIL
- A two-arity UNTIL is genuinely a natural fit for readability in many situations by separating the condition and body blocks.
- Single-arity UNTIL's popularity "in the wild" is vastly lower than WHILE, making it less disruptive to change.
- It is not difficult for codebases which worked with the old definition to work around it with a very simple
until: :loop-until
, pending a simple search-and-replace to fix the usages.