Shall we CONTINUE?

At some point I added the ability of CONTINUE to take a parameter. The idea was that CONTINUE with a parameter would act the same as if the loop body had completed with that value. One place this is most useful is MAP-EACH:

>> map-each x [1 2 3] [
       if x = 2 [continue <twenty>] else [x * 10]
   ]
== [10 <twenty> 30]

But what should CONTINUE with no argument do? In the MAP-EACH case, I think it should pretty obviously not add anything to the output.

>> map-each x [1 2 3] [
       if x = 2 [continue] else [x * 10]
   ]
== [10 30]

Nice. But what about "ordinary" loops?

How about WHILE or REPEAT or FOR-EACH? If we wanted it to be novel, it could say "don't change the pending overall result from whatever the last loop iteration would have returned":

>> for-each x [1 2 3] [
     if x = 3 [continue] else [x * 10]
]
== 20

Although I like that in theory, it would break the loop composition rules unless loops were allowed to be invisible. :-/

Think about the code I've previously written down to implement FOR-BOTH:

for-both: func ['var blk1 blk2 body] [
    unmeta all [
        meta for-each var blk1 body
        meta for-each var blk2 body
    ]
]

That FOR-BOTH could not have a CONTINUE that would run in the body of the second FOR-EACH and carry over a value from the first FOR-EACH.

Given that we'd be setting a standard that would be difficult to follow, I think the answer has to be:

>> for-each x [1 2 3] [
     if x = 3 [continue] else [x * 10]
   ]
== ~none~  ; isotope

What about UNTIL and CONTINUE TRUE?

I've given a rule that CONTINUE passed a parameter effectively jumps to the end of the loop body as if it had finished with that value. But in UNTIL, the loop's body also is the condition. So what about:

>> until [print "A" if true [continue true] else [<unreachable>]] print "B"
A
B

It seems to make a certain amount of sense.

2 Likes

So what I'd missed here in the composition rules is a wrinkle that if you try the FOR-BOTH construct and the second list is empty, then you wind up overwriting the last result from the first list. It creates the same kind of problem as if you wanted a CONTINUE with ~none~ to preserve the last value.

The tool to tackle this would be something that vanishes plain ~none~ after a META, or a version of meta that elides ~none~ isotopes. So:

for-both: func ['var blk1 blk2 body] [
    unmeta all [
        meta for-each :var blk1 body
        none-to-void/meta meta for-each :var blk2 body
    ]
]

or:

for-both: func ['var blk1 blk2 body] [
    unmeta all [
        meta for-each :var blk1 body
        meta/none-to-void for-each :var blk2 body
    ]
]

Naming is open to discussion there. Could be META/VANISHABLE or somesuch. You can also make it symmetrical by doing it for both loops, it just doesn't matter because the ALL will give ~none~ isotope if the body turns up empty:

for-both: func ['var blk1 blk2 body] [
    unmeta all [
        meta/vanishable for-each :var blk1 body
        meta/vanishable for-each :var blk2 body
    ]
]

The consequence of this, though, is that you can't force a ~none~ isotope return result from a loop once the body has returned any non-~none~-isotope value. You'd have to do something like a CATCH with a THROW of ~none~.

I can't offhand tell you if that's annoying or a feature. You could always META your body result and UNMETA it outside the loop.