REBOL3 tracker
  0.9.12 beta
Ticket #0002146 User: anonymous

Project:

Previous Next
rss
TypeWish Statusdismissed Date6-May-2014 01:32
Versionr3 master CategoryNative Submitted byfork
PlatformAll Severityminor Prioritynormal

Summary Add single-arity variant of WHILE, two-parameter version of UNTIL
Description WHILE and UNTIL are closely related but rather different:

a: 1
while [a < 10] [
print a
a: a + 1
]

a: 1
until [
print a
a: a + 1
a = 10
]

The fact that UNTIL takes a single block is a cool play on Rebol's ability to easily get the last value in an evaluated block. But the two don't match. And we're missing their siblings: a single-argument WHILE and a double argument UNTIL:

a: 1
while-1arg [
a < 10
print a
a: a + 1
]

a: 1
until-2arg [
print a
a: a + 1
] [a = 10]

It is technically possible to implement the 1-arg variant of WHILE as a mezzanine:

while-1arg: func [cond+code [block!] /local code] [
while [do/next cond+code 'code] [
do code
]
]

If written as a native using Do_Next, it could be written as no less efficient than the single-arity UNTIL... would shave off a series in source, and perhaps be more aesthetically pleasing to some users

One question would be about the naming. To me the "weirder" constructs in both cases are the single-arity ones; those are going to be the least familiar to anyone coming from another language. I'd use the typical names for the two-arity versions, and odder names to the single-arity ones to call attention.

In thinking about how to express that, I had the idea of going British/Shakespearean and calling the single-arity version of WHILE "WHILST". That made me wonder if the double-arity version of until could be something fun like "UNTILTH", and reclaim UNTIL for the more "boring" two-parameter variant.

Not married to the names though.
Example code

			

Assigned ton/a Fixed in- Last Update21-Aug-2014 02:17


Comments
(0004413)
fork
6-May-2014 01:43

To pre-empt a potential objection:

"Rebol's evaluator does not (without dialecting tricks of DO/NEXT) offer the ability to get the result from evaluating the first expression in a block. It only offers the last. It is therefore un-Rebol-like and misleading to have a construct like the single-arity WHILE."

I would ask if that same argument might be applied to turn people against ANY. It is able to short-cut an evaluation:

>> any [a: 10 b: 20]
== 10

>> print b
** Script error: b has no value

Does the existence of ANY, as not being something you could purely accomplish with (say) REDUCE, undermine the language?

I'd say that Rebol is supposed to be full of dialects and tricks to give you the language you want. Refusing a single-arity WHILE, when a single-arity UNTIL has proven useful, seems like allowing the interpreter-tail to wag the language-dog.

Original motivation for wanting this was that I had decided I was doing a "bad thing" in Rebmu. I'd made it so that for non-block values in the condition slot of a while, they would be evaluated. Hence this would print "loop" and terminate:

a: true
while-mu a [
print "loop"
a: false
]

Unlike the value behaviors in IF-MU, I do not think that has much chance of making it into the language, for reasons that are likely good. (Although I do think that being willing to accept a function value, in addition to a block, might be useful.) I realized what I actually wanted was the single arity while--it's more flexible because it works with expressions, and even in the single-word case it compresses equally well:

a: true
while-1arg [
a
print "loop"
a: false
]

So then I thought "hey, why doesn't that exist already?" It further helps the mission of making the tricks that are taught in Rebmu not be "Rebmu tricks", but genuinely learning things you can carry over to Rebol.
(0004417)
fork
10-May-2014 10:44

WHILST added to incubator.reb, the much lamer WHILE-MU dead:

https://github.com/hostilefork/rebmu/blob/14dc62b1e1b72c5362e4b33155e77a2cfefeb447/incubator.reb#L53

Having worked in the medium, I think it should exist. I just don't know what it should be called.
(0004419)
fork
10-May-2014 23:22

Upon reflection, I wonder if a 2-arg UNTIL should put the condition as the first or second argument. :-/ It feels to me like it would be comprehensible as a first argument, and print a line in this case:

x: 2
until-2arg [x = 2] [
print "condition not tested until end of loop"
]

Though that would not match up with C's "do...while", and also be asymmetric with the 1-arg UNTIL putting the condition last. I just want to point it out as a design choice: if all things were equal and you were just thinking about UNTIL in a Rebol model, and had to put the condition somewhere, does it logically come as the second argument?

Perhaps it does.
(0004420)
Gregg
12-May-2014 19:17

My gut says a 2 arg UNTIL sits well, but not a 1 arg WHILE. I rarely use UNTIL, I think because the design just doesn't click in my brain, and also because the condition is separated from the clause.
(0004432)
fork
2-Jun-2014 22:43

I do agree, Gregg, that the existing definition of UNTIL is counterintuitive and it should be changed to a 2-arg version as the default. Whether the condition comes first or second is up for debate, as is the issue of whether the 2-arg version should return the last condition evaluation or the last code evaluation. Asssuming condition second, what should this return?

y: 0
result: until [
print y
++ y
"Is this what you want?"
] [y = 10]
print result

The current behavior of the 2-arity while is to return the last code evaluation, not the last condition evaluation.

My Shakespearean naming stuff was kind of silly, I was just looking in the thesaurus and came across WHILST. But upon reflection, I came up with WHILE-FIRST and UNTIL-LAST, and immediately ditched WHILST and UNTILLETH.

However, WHILE-FIRST has the "sketchy semantics" I mentioned. I cited ANY/ALL (CASE would count also) for precedent of things that go outside the typical evaluation system to get their job done...and are cool. But whereas UNTIL-LAST could take a function argument instead of a block, that is not possible for WHILE-FIRST as it needs to "dig into" the code too much and subvert the evaluator.

After discussion with @earl, he feels WHILE-FIRST is "unfit" for anything besides code golf and should not be in the box. If that is the case, then the single-arity UNTIL should probably be called just UNTIL-TRUE, which is natural enough. Framed that way, no parallel can be made for a single-arity WHILE.

So the new tentative plan would be:

* Rename what is currently known as UNTIL to UNTIL-TRUE
* Decide whether condition should be first or second in 2-arity UNTIL, which takes over the name UNTIL
* Decide whether 2-arity UNTIL returns last evaluation of the code block (to align with WHILE) or last evaluation of the condition (to align with UNTIL-TRUE)
* Relegate WHILE-FIRST to being a Rebmu construct, whose main purpose is code golf (even though I've been trying to eliminate the "mu library" offering golfing tools, in favor of a higher priority of teaching "real Rebol")
(0004451)
BrianH
22-Jun-2014 00:19

I also rarely use UNTIL, but this is mostly because having a condition that also serves as a return value is of limited use. I find it much better to use WHILE in post-condition mode.

Rebol's WHILE function isn't a pre-condition loop, it's a mid-condition loop. You can have a variety of statements in the first block, only the last is treated as the condition. There can be a variety of statements in the second block as well, only the last is used for the return value. It's a really flexible function. WHILE can easily be used in post-condition mode by putting the other statements in the first block before the condition. UNTIL can be used in pre-condition mode by using IF and saving the results of the condition to evaluate at the end of the block. So, in terms of pre-vs-post condition use there's really not much difference between the two.

It seems to me that the main difference between WHILE and UNTIL is that for UNTIL the condition value is also returned, but for WHILE a different value is returned. That seems to me to be the main value of UNTIL having one block and WHILE having two.

Another advantage to having one or two blocks is for expressions that return code blocks, like the one in REPLACE. If you need to return one block with both condition and other code, you need a function that takes both in one - the current UNTIL will serve that purpose, either for pre or post condition code. If you need to return just the condition or other-code block, or return them separately from two expressions, then you'll need the two-block form or explicit DO of block values. But this kind of thing is rare, and we have workarounds for anything awkward.

Ignoring the code golf case, there's nothing we're missing from the current functions that we can't do already. Doesn't seem worth breaking compatibility for.
(0004484)
fork
21-Aug-2014 02:10

After considering it a while, I think that WHILE and UNTIL sound like IF and UNLESS.

x: 1
while [x = 1] [print "Hello"]

...should print "Hello" forever.

x: 1
until [x = 1] [print "Hello"]

...should print nothing. In both cases, the tests done on each loop iteration, once before the first iteration. That has symmetry.

Both cases have a useful analogue: do nothing until the condition transitions from true to false (for while) or false to true (for until). You get this merely by putting all of your code in the condition and leaving the body blank.

Both cases also have the question of whether the body should be run at least once before the check is applied. An /AFTER refinement could position the check to be after the body has been run, instead of before.

Also the question becomes what result do you want the expression to evaluate to: the non-FALSE result from the condition, or the (possibly unset) result of the last body evaluation? Biasing to the last value from the body seems sensible, in part because it can be decoupled from the check. (The only values you could get back from a while would be NONE or FALSE!)

That all looks good except for the question of how to do a unary version, as well as "is a unary version really needed when you can just leave the body blank and put all your code in the condition?" So is this really that terrible:

until [
something
something
something
condition
] []

I don't think it's that bad, *and* I think that having UNTIL be a nice natural analogue of WHILE would get it more use and gather up the spirt of UNLESS.

The big question would be what to do when you wanted to return the condition as a result. But you could just write:

until/after [x] [
something
something
something
x: condition
]

Date User Field Action Change
21-Aug-2014 02:17 Fork Status Modified reviewed => dismissed
21-Aug-2014 02:10 Fork Comment : 0004484 Added -
22-Jun-2014 00:20 BrianH Category Modified Unspecified => Native
22-Jun-2014 00:20 BrianH Status Modified submitted => reviewed
22-Jun-2014 00:19 BrianH Comment : 0004451 Added -
2-Jun-2014 22:43 Fork Comment : 0004432 Added -
12-May-2014 19:17 Gregg Comment : 0004420 Added -
10-May-2014 23:22 fork Comment : 0004419 Added -
10-May-2014 10:44 Fork Comment : 0004417 Added -
6-May-2014 01:43 Fork Comment : 0004413 Added -
6-May-2014 01:32 Fork Summary Modified Add single-arity variant of WHILE => Add single-arity variant of WHILE, two-parameter version of UNTIL
6-May-2014 01:32 Fork Description Modified -
6-May-2014 01:32 Fork Ticket Added -