I showed in my previous post how to react to the result of a loop, based on the knowledge that it returns (VOID!) if it never runs. This lets you transform:
if condition [ while [condition] [ stuff to do in the loop ] ] else [ stuff to do if condition was false to start with ]
while [condition] [ stuff to do in the loop true ] or [ stuff to do if condition was false to start with ]
That’s pretty neat. And it’s not just about avoiding repetition for the sake of brevity… your condition might want to have side effects, so having a way of phrasing it that you don’t double-execute it could be semantic as well.
But there’s another signal to know about, and that’s that a loop which BREAKs returns null. And if the body of a loop returns null, that will be turned into a VOID!:
>> for-each item [1 2 3]  == #[void]
But you’re not limited to this. As long as you’re willing to have your body return some non-null/non-void thing, you have three signals:
- null -> loop was broken, didn’t run to completion
- void -> loop body never ran, not even once
- non-void thing -> loop made it to the end
…and it’s Awesome
There are lots of patterns at your disposal.
Remember that DID and NOT now treat nulls as false. So you can squash together the null and blank states.
opt some-loop [ ... true ] else [ code to run if loop had a BREAK, or body never ran at all ] opt some-loop [ ... true ] then [ code to run if loop ran at least once, and didn't break ] some-loop [ ... ] else [ code to run if loop BREAKs ]
The sky’s the limit with these things:
opt some-loop [ ... true ] then [ code to run if loop ran at least once ] else [ code to run if loop never ran, or break ]
Here’s a real world case from the console code:
pos: molded: mold/limit :v 2048 loop 20 [ pos: (next find pos newline) else [break] ] also [ insert clear pos "..." ]
You have up to 2048 characters of data coming back from the mold, ok. Now you want just the first 20 lines of that. If truncation is necessary, put an ellipsis on the end.
loop 20 obviously will always try and run the body at least once. (So the loop will never return void here.)
FIND will return blank if it can’t find the thing you asked it, then NEXT of a blank is null. So, the ELSE runs when you try to get the NEXT of a blank, but cannot. If it makes it up to 20 without breaking, then the ALSO clause runs.
So there you go. The first 20 lines of the first 2048 characters of a mold, truncating with “…” I think the ALSO really follows the idea of completion, it makes sense (to me) that a BREAK would bypass an ALSO clause.
I encourage people to get creative, to look at ways to use this to write clearer/shorter/better code.