It really feels like the loop constructs in Inform 7 are half baked. We have while but not until, repeat with x from n to m can’t be used to count down, and there’s no repeat with x running backwards through thelist. Has anyone made an extension to improve on this?
More to the point, assuming such an extension has not been made yet, is it even possible to replace the definition of the built-in repeat with? I mean, other than by replacing the entire “Section 3 - While and Repeat”, which would require duplicating the definitions of while and foreach loops.
Since this came up in the reference manual thread a year or so ago, I already have code that addresses some of the above concerns; I’m just wondering if anyone ever turned that code into an extension.
Other than until, the repeat with x from n to m case is particularly annoying, because while you could easily define a version for arithmetic values that counts down to instead, that doesn’t work very well for arithmetic values. There’s a pretty good chance you’d accidentally put your arithmetic values in the wrong order and wonder why nothing is happening.
EDIT: Glancing at Inform 6 documentation reminded me of another missing construct: the do ... while/until loop. I can’t think of anyway to create this in Inform 7 though – to my knowledge there isn’t a way to put a “suffix” on the loop body…
We often want to arrange for something to happen at some point in the future. Here is yet another timepiece:
An egg-timer is in the Chamber. "A plastic egg timer in the shape of a chicken can be pressed to set it going."
Instead of pushing the egg-timer:
say "It begins to mark time.";
the egg-timer clucks in four turns from now.
At the time when the egg-timer clucks:
say "Cluck! Cluck! Cluck! says the egg-timer."
I also believe @Draconis has made an improved timer extension.
There have been some extensions adding “until” loops, but they require messing with Preform, which means they’re currently very difficult to install and use. That’s hopefully going to change with the next release of Inform.
For do…until loops, the easiest way is to use one of those extensions that adds a “repeat forever” loop, and then do this:
repeat forever:
do stuff here;
if the condition is met, break;
I’ll try to find which of those extensions is in the best working order at this point. There have been a few different attempts at it. It’s fortunately not too hard to define new looping constructs in I7 compared to, say, I6.
I’m not sure I understand your concern about putting the beginning and end in the wrong order. Are you talking about arithmetic values, like “24kg”, or enumerated values, like “green”?
The concern about putting them in the wrong order was for enumerated values. There’s also valid reasons to want to count downwards with arithmetic values, but if that were the only issue I could trivially define repeat with N running from 10 down to 4 instead.
To repeat with/for the/a/an/-- (loopvar - nonexisting K variable) running/-- from/in (v - value of kind K) down to (w - K) begin -- end loop:
(- for ({loopvar}={v}: {loopvar}>={w}: {loopvar}-- ) -).
To repeat with/for the/a/an/-- (loopvar - nonexisting K variable) running/-- from/in (v - value of kind K) down to (w - K) with/using index (index - a nonexisting number variable) begin -- end loop:
(- for ({loopvar}={v}, {index} = 1: {loopvar}>={w}: {loopvar}--, {index}++ ) -).
lab is room.
color is a kind of value. colors are red, orange, yellow, green, blue, indigo, violet.
when play begins:
repeat for i from 3 down to 1 begin;
say "[i].";
end repeat;
repeat for j from 8 down to 5 with index k begin;
say "[k]. [j].";
end repeat;
repeat for c from indigo down to orange begin;
say "[c] ";
end repeat;
Right, but if you’re likely to forget the order of your enumerated values, you’re also likely to forget which ones are in between which others, I’d think? It’s very reasonable to forget whether “sweet” comes before “salty” in your enumeration, but I’d think you’d just as easily forget whether “sour” is between the two or somewhere outside.
@Draconis What are you saying? If you look at repeat with N running from sweet to sour, it’s impossible to guess what comes between them without hunting down the definition of that enumerated value, but if you look at repeat with N running from 10 to 3, it’s obvious that the intent was to count down (even if it didn’t work).
@Zed Yes indeed, I already figured that out; what I was hoping for was something that would also work for enumerated values without being confusing, because it’s not obvious what the order of those values is just by looking at the loop. (By the way, I don’t quite understand the purpose of your “using index” variation…?)
Actually, the code I have right now is something like this:
To repeat with (loopvar - nonexisting K variable) running from (v - arithmetic value of kind K) to (w - K) begin -- end loop: (-
for({loopvar}={v}: CheckLoopVar({loopvar},{v},{w}): {loopvar}=NextLoopVar({loopvar},{v},{w}))
-).
To repeat with (loopvar - nonexisting K variable) running from (v - enumerated value of kind K) to (w - K) begin -- end loop:(-
for({loopvar}={v}: CheckLoopVar({loopvar},{v},{w}): {loopvar}=NextLoopVar({loopvar},{v},{w}))
-).
Include (-
[ CheckLoopVar var start end;
if(start < end && var <= end) rtrue;
else if(start > end && var >= end) rtrue;
else if(start == end) return var == start;
rfalse;
];
[ NextLoopVar var start end;
if(start <= end) return var + 1;
return var - 1;
];
-).
Plus a section replacement that deletes the default definitions of those two phrases from Basic Inform. But that section replacement is literally “copy-paste the entire section and delete two phrases”, which is awful. I was hoping for a better way to do it.
Of course, I can define an entirely different phrase instead and just not use the built-in one. But that wasn’t the question here.
To repeat until (c - a condition) begin -- end loop:
(-
for ( {-my:0} = 1 : {-my:0} || (~~({c})) : {-my:0} = 0 ) {-block}
-)
lab is room.
when play begins:
let x be 5;
repeat until x < 10 begin;
say "once: [x].";
end repeat;
repeat until x is 2 begin;
decrement x;
say x;
end repeat;
I’m not sure what you’re hoping for. If the order of the enumerated values is confusing that’s semantics and syntax isn’t going to cure it.
[Edited: wait, is the desire to have a loop construct that takes two enumerated values and then automatically goes upward or downward depending on their relation?]
Just to have a loop counter without making it manually.
Oh, cool! So there is a way to get something like a “repeat … while/until” look that always runs at least once. The only remaining issue then would be how to make it not be confusing, since the condition has to appear at the top even though it’s checked at the bottom
That was my thought, yes. If you have code like the following:
A colour is a kind of value.
The colours are red, green, blue, yellow, orange, purple.
When play begins:
repeat with C running from orange to green:
say "[C].";
Maybe you expected it to print “orange, purple, red, green”, or maybe you forgot the order of the colours. I think it’s less confusing to see it print “orange, yellow, blue, green” than it is to see it print nothing at all. Or to put it another way, seeing it do something makes it easier to debug than seeing it do nothing. (This applies to numbers too, though less so – you can easily see that you wrote 10 to 5.)
Though actually, it would be just as valid to make it print “orange, purple, red, green”… if there were a separate down to loop, I think I’d expect that kind of wrapping behaviour for an enumerated value.
To be clear, I meant coming up with a wording that makes it clear that the condition is checked at the end despite appearing first. Though, it might truly be impossible to find a good wording for that…
My code above was only tested on numbers, but it does work, so I presume it would also work on enumerated values… though I’m now starting to wonder whether “counting down” or “wrapping around” is a better mechanic in this situation…
Include (-
<control-structure-phrase> ::=
if ... is begin |
if ... is |
if/unless ... |
repeat/loop ... |
while ... |
else/otherwise |
else/otherwise if/unless ... |
else/otherwise ... |
-- otherwise |
-- ...
<end-control-structure-phrase> ::=
end if/unless |
end while |
end repeat/loop
-) in the Preform grammar.
To loop at least once but don't keep going if (c - a condition) at the bottom begin -- end loop:
(-
for ( {-my:0} = 1 : {-my:0} || (~~({c})) : {-my:0} = 0 ) {-block}
-)
Include (-
<control-structure-phrase> ::=
if ... is begin |
if ... is |
if/unless ... |
repeat ... |
while/do ... |
else/otherwise |
else/otherwise if/unless ... |
else/otherwise ... |
-- otherwise |
-- ...
<end-control-structure-phrase> ::=
end if/unless |
end while/do |
end repeat
-) in the Preform grammar.
To do begin -- end loop: (- while(1) {-block} -)
To until (c - condition): (- if ({c}) break; -)
lab is room.
when play begins:
let x be 5;
do begin;
say "once: [x].";
until x < 9; end do;
do begin;
decrement x;
say x;
until x < 1; end do;
Oh, that’s kind of ingenious… though the until phrase definition should probably have -- in loop, and I don’t think that syntax will work well with colon-and-indentation.
Is it weird that I’ve never heard of an until loop before? For context, I finished my BS in CS in December of 2016, C++ is my primary language, I’ve also taken courses in Java, x86 Assembly, and Visual Basic though Java is probably the only of those three I’d have any chance of parsing, and I’ve had some exposure to Python.
Sure, that buys us an I7 error for a misplaced statement, which is preferable, instead of an I6 error.
It doesn’t work with colon-and-indentation at all (but I don’t use that).
To repeat for/-- (n - a number) time/times begin -- end loop:
(- if ({n} > 0) for ({-my:0} = {n} : {-my:0} : {-my:0}-- ) -).
You might like to look at my Strange Loopiness extension. I never ended up releasing a 10.1 version because some of the code wouldn’t work anymore, but much does.
It had two separate repeat loops through lists backwards, one for texts and one for everything else. The improved version below does it all in one by testing for block-values.
To repeat with/for the/a/an/-- (loopvar - nonexisting K variable)
running/-- through/in (l - list of values of kind K) in/-- reverse/backwards order/-- begin -- end loop: (-
{-my:0} = LIST_OF_TY_GetLength({l});
for ({-my:1} = {-my:0} : {-my:1} >= 1 : {-my:1}-- )
if ({-by-reference:loopvar} = LIST_OF_TY_GetItem({l}, {-my:1}))
if ((KOVIsBlockValue({-strong-kind:K}) && BlkValueCopy({-by-reference:loopvar}, {loopvar})) || 1)
-).