In the last moments of 2023, Ren-C finally bit the bullet and made BREAK and CONTINUE definitional (it was a long time coming, just hadn't gotten around to it...):
Definitional Break and Continue: The Time Is Now
This means that loops create (optimized) variants of BREAK and CONTINUE in which the ACTION! cell has been tweaked to hold the identity of the loop.
The methodology for this optimized representation was pioneered with definitional RETURN.
Except in this case, there are two new "LET-style variables" tacked onto the loop's binding environment... one for BREAK, and one for CONTINUE.
It Would Be Nice To Have AGAIN
It seems to me pretty powerful to be able to ask a loop to start again from the top, but not increment its loop index...or check the condition, or whatever.
(This has precedent in other languages, e.g. Perl's REDO... but I like AGAIN better.)
That would add a third LET-variable. ![]()
But Could It Be Just ONE LET-Variable?
As I've realized the kind-of-awesome power of virtual binding, I realized that it might be the case that there was just one "throw to loop" construct, that takes different parameterization:
-
throw-to-loop ~(veto)~=> BREAK- VETO is a function that returns an ERROR! with the identity 'VETO... which is an awesome generalization I haven't talked about yet, that makes constructs abort and return NULL. It works inside things like REDUCE (e.g.
reduce [1 + 2 veto]=>null) but also inside GROUP!s of code in PARSE to allow match failures to be signaled by a GROUP! (whose product would otherwise be discarded). It seems the perfect argument for THROW-TO-LOOP to mean "let's abort this whole loop and give null".
- VETO is a function that returns an ERROR! with the identity 'VETO... which is an awesome generalization I haven't talked about yet, that makes constructs abort and return NULL. It works inside things like REDUCE (e.g.
-
throw-to-loop ()=> CONTINUE- This would have the same effect as reaching the end of the loop body and synthesizing VOID. So if you were doing a MAP-EACH, an iteration of the loop that ran CONTINUE would not contribute anything to the result.
-
throw-to-loop ~(again)~=> AGAIN-
I don't know if RETRY should be a function that just returns an ERROR! the way VETO is so I'm hand-waving a little here to say "let's have another special trigger that loops can respond to"... if they don't support it, they can treat it like any other error.
-
I'm not thrilled that "retry" doesn't sound like an "error" name the way VETO kind-of-does, but there's only so many unstable antiforms to choose from (and VOID seems random to pick to mean "try again"). Maybe INCOMPLETE is a better error ID... where the loop goes "oh, the failure was it didn't complete... so the natural response is to try again"?
-
-
throw-to-loop <whatever>=> CONTINUE:WITHCONTINUEhas had a refinement called:WITHthat allows you to act as if the loop body completed with some other value besides VOID. Socontinue:with spread [d e]inside a MAP-EACH would add the splice~(d e)~to the mapped result. I don't know if it's better to have this:WITHrefinement or if you should just use THROW-TO-LOOP directly... (could THROW-TO-LOOP have a better name?)
One Way To Do It... Macros!
break: macro [throw-to-loop ~(veto)~]
continue: macro [throw-to-loop ()]
again: macro [throw-to-loop ~(again)~]
While we'd most likely want to nativize CONTINUE, BREAK, and AGAIN... maybe MACRO can be smart enough to produce native-speed code for this when not running under a stepwise debugger...
Any Submissions For Better Names For THROW-TO-LOOP?
Maybe being bluntly literal is best.
But @BlackATTR suggested TOSS, PASS, PITCH, PUNT, FLICK
They all sound weird to us now, but everything has a learning curve. You learned what BREAK and CONTINUE meant, you'd learn what AGAIN meant. Could you learn what TOSS meant, that it was specifically a THROW targeting a LOOP...?
Leaving that open for now.
Or... Variadic CONTINUE?
I'll also point out that once-upon-a-time, CONTINUE was variadic... so you could say continue 10 and if you left off the parameter it would assume you meant VOID. This was too error prone, due to line continuation bugs:
Line Continuation and Arity Bugs: Thoughts?
But I've been thinking maybe you have to continue lines with a backslash. So this would error:
append [a b c]
[d e f]
But this would be legal:
append [a b c]
\ [d e f]
And presumably this would be, too (though it's inferior...)
append [a b c] \
[d e f]
The reason it would be legal is it would actually LOAD the code as not having a line break marker.
Then we'd just make line break markers illegal outside of interstitial evaluations. (Maybe relax it, so that if you were inside a GROUP! evaluation it would allow it, though that might make it toothless.)
(append [a b c]
[d e f])
If the system got more persnickety about line continuation, then we might feel comfortable bringing back variable-arity CONTINUE, QUIT, RETURN...
I doubt we want to go down the route of JavaScript's automatic semicolon insertion debacle, and act like there's a comma at "some" line end markers.
I have a hard time being psychic about whether this is a big creativity-enabler (by letting us be more purposeful about semantics of line continuation markers) or if it would lead to hassles. Overall I feel like it would cut down on bugs, by making line endings usually mean expression endings... and having you be specific when that's not what you want.
