www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - [Language construct idea] The for loop with prepare step

reply Quirin Schroll <qs.il.paperinik gmail.com> writes:
Extend `for` to `for (init; prepare; condition; increment) body`.

The step `prepare` is executed before each check. Ignoring scope, 
the above is lowered to:
```d
     init;
start:
     prepare;
     if (!condition) goto end;
     body;
     increment;
     goto start;
end:
```
`continue` is also `goto start`.

Sometimes, you want to execute something before each condition 
check, no matter if it’s when entering the loop or when looping 
back.

D’s best options for that is:
* Repeat yourself in `init` and `increment`, possibly using a 
local function if the step is more than 1 simple instruction.
* Use `{ prepare; return condition; }()`. While not a DRY 
violation, this has the disadvantage that you cannot declare a 
variable in `prepare` that’s present in the loop’s `increment` or 
`body`.
Feb 12
next sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Monday, 12 February 2024 at 15:58:50 UTC, Quirin Schroll wrote:
 Extend `for` to `for (init; prepare; condition; increment) 
 body`.

 The step `prepare` is executed before each check.
I've never heard of anything like this in any other programming language, so I'm skeptical by default. Do you have any examples of code that this feature would improve?
Feb 12
parent Quirin Schroll <qs.il.paperinik gmail.com> writes:
On Monday, 12 February 2024 at 16:14:40 UTC, Paul Backus wrote:
 On Monday, 12 February 2024 at 15:58:50 UTC, Quirin Schroll 
 wrote:
 Extend `for` to `for (init; prepare; condition; increment) 
 body`.

 The step `prepare` is executed before each check.
I've never heard of anything like this in any other programming language, so I'm skeptical by default. Do you have any examples of code that this feature would improve?
I thought it was obvious, but okay: Instead of writing something like: ```d import core.stdc.stdio; for (int c; (c = fgetc(fp)) != EOF; ) { } ``` once could do: ```d for (import core.stdc.stdio; int c = fgetc(fp); c != EOF; ) { } ``` Example source: https://en.cppreference.com/w/c/io/fgetc (There, `c` is only used in the loop, and the declaration of `c` directly precedes the `while` loop only because in C89, `for` loops cannot use declarations.) It’s a standard example of a probably not that niche pattern. I’ve written this loop a lot of times learning and doing projects with C.
Feb 13
prev sibling parent reply "H. S. Teoh" <hsteoh qfbox.info> writes:
On Mon, Feb 12, 2024 at 03:58:50PM +0000, Quirin Schroll via Digitalmars-d
wrote:
 Extend `for` to `for (init; prepare; condition; increment) body`.
 
 The step `prepare` is executed before each check. Ignoring scope, the
 above is lowered to:
 ```d
     init;
 start:
     prepare;
     if (!condition) goto end;
     body;
     increment;
     goto start;
 end:
 ```
 `continue` is also `goto start`.
 
 Sometimes, you want to execute something before each condition check,
 no matter if it’s when entering the loop or when looping back.
 
 D’s best options for that is:
 * Repeat yourself in `init` and `increment`, possibly using a local
 function if the step is more than 1 simple instruction.
 * Use `{ prepare; return condition; }()`. While not a DRY violation,
 this has the disadvantage that you cannot declare a variable in
 `prepare` that’s present in the loop’s `increment` or `body`.
The primary issue here is that the conventional looping constructs (the age-old for, while, do) enter the loop at the top, and exit the loop at the bottom. However, your general loop may exit the loop in the middle. (The other possibility -- entering the loop in the middle -- is equivalent to this case via a simple code rearrangement.) Therefore, you have this structure: loopStart: loopBodyBeforeCondition(); if (cond) break; // loop exit loopBodyAfterCondition(); goto loopStart; When loopBodyBeforeCondition is empty, you have a while-loop. When loopBodyAfterCondition is empty, you have a do-loop. When loopBodyAfterCondition only contains incrementing code, you have a for-loop (the initialization part of a for-loop technically belongs outside the loop, just before it begins). However, there is no direct equivalent to the case when neither loopBodyBeforeCondition nor loopBodyAfterCondition are non-empty (and the latter isn't just for incrementing). In such cases, most code resorts to hacks like `while(true)` with manual exit points. One example of the usefulness of a loop construct that supports the above construct in full generality is the outputing of list delimiters. If we postulate a syntax construct like: loop { loopBodyBeforeCondition(); } while(cond) { loopBodyAfterCondition(); } then, given some range r of items, we can output it with delimiters like this: loop { writef("%s", r.front); r.popFront; } while(!r.empty) { write(", "); } However, since we don't have such a construct, we have to resort to various hacks, like: while (!r.empty) { writef("%s", r.front); r.popFront; if (!r.empty) // <-- non-DRY write(", "); } Or: string delim; // hack: need an extra variable while (!r.empty) { writef("%s%s", r.front", delim); delim = ", "; // hack: lots of redundant assignments r.popFront; } Or: if (r.empty) return; writef("%s", r.front); // hoist first iteration out of loop body (non-DRY) r.popFront; while (!r.empty) { writef(", %s", r.front); // fold loopBodyAfterCondition into front of loop r.popFront; } The asymmetry of these hacks is caused by the underlying non-correspondence between the desired structure of the output (delimiter is placed between every pair of items) and the available structure of looping constructs in the language (none of the existing looping constructs exit in the middle of the loop body). Having a construct like proposed above solves the problem by making available a construct that has a 1-to-1 correspondence with the desired structure of the output. T -- Genius may have its limitations, but stupidity is not thus handicapped. -- Elbert Hubbard
Feb 12
parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 13/02/2024 6:52 AM, H. S. Teoh wrote:
 One example of the usefulness of a loop construct that supports the 
 above construct in full generality is the outputing of list delimiters. 
 If we postulate a syntax construct like:
 
 
 |loop { loopBodyBeforeCondition(); } while(cond) { 
 loopBodyAfterCondition(); } |
 
 
 then, given some range r of items, we can output it with delimiters like 
 this:
 
 
 |loop { writef("%s", r.front); r.popFront; } while(!r.empty) { write(", 
 "); } |
Oh I quite like this. ```d do { } while(cond) { } ``` ```d foreach(...) { } while { } ``` I would use this a lot.
Feb 12
next sibling parent reply Quirin Schroll <qs.il.paperinik gmail.com> writes:
On Tuesday, 13 February 2024 at 00:59:36 UTC, Richard (Rikki) 
Andrew Cattermole wrote:
 On 13/02/2024 6:52 AM, H. S. Teoh wrote:
 One example of the usefulness of a loop construct that 
 supports the above construct in full generality is the 
 outputing of list delimiters. If we postulate a syntax 
 construct like:
 
 
 |loop { loopBodyBeforeCondition(); } while(cond) { 
 loopBodyAfterCondition(); } |
 
 
 then, given some range r of items, we can output it with 
 delimiters like this:
 
 
 |loop { writef("%s", r.front); r.popFront; } while(!r.empty) { 
 write(", "); } |
Oh I quite like this.
Me, too. I love this: ```d do { … } while (cond) { … } ``` It’s a lot more structured than: ```d for (;;) { … if (!cond) break; … } ``` The only issue I see is it would probably be useful to have the variables declared in the top part be in scope in the lower part, however that is counterintuitive because of separated blocks. The less structured construct solves this. In my 4-statement `for` loop, it’s not surprising that a variable declared in the prepare step is present in the loop body. Alternatively, one can double-down on D’s for loop weirdness and allow this: ```d for ({int i = 1; int n = 10;} i < n; ++i) { } // already possible for (int i = 0; {int n = 10 - i; i < n;} ++i) { } // new! ``` Thinking about it, I actually like that more. The small semicolon could really easily be missed, while the braces are striking. I know I’ve seen this in some diagrams at university. Probably some form of Nassi–Shneiderman diagram. The University of Darmstadt has one here: https://www.iim.maschinenbau.tu-darmstadt.de/kursunterlagen_archiv/pst_ws1314/04-Methoden-3/Theorie/na sishneidermann.html (German) Image only:https://www.iim.maschinenbau.tu-darmstadt.de/kursunterlagen_archiv/pst_ws1314/04-Methoden-3/Theorie/endlosschleife.png On the other hand, this is rather obscure and I don’t intuit how it’ll work: ```d foreach(x; xs) { … } while { … } ``` This is my attempt to express it in terms of a `while` loop and range primitives: ```d for (;;) { … if (!xs.empty) break; auto x = xs.front; … xs.popFront; } ``` It’s weird to do stuff before you check a range for empty. It’s fine explicitly, but it would be weird to have `x` not be there in the first block after `foreach`. Correct me if I got the lowering wrong.
Feb 13
parent Quirin Schroll <qs.il.paperinik gmail.com> writes:
On Tuesday, 13 February 2024 at 11:39:56 UTC, Quirin Schroll 
wrote:
 […]

 Alternatively, one can double-down on D’s for loop weirdness 
 and allow this:
 ```d
 for ({int i = 1; int n = 10;} i < n; ++i) { } // already 
 possible
 for (int i = 0; {int n = 10 - i; i < n;} ++i) { } // new!
 ```
I filed this as an enhancement: https://issues.dlang.org/show_bug.cgi?id=24395
Feb 15
prev sibling parent reply "H. S. Teoh" <hsteoh qfbox.info> writes:
On Tue, Feb 13, 2024 at 01:59:36PM +1300, Richard (Rikki) Andrew Cattermole via
Digitalmars-d wrote:
[...]
 ```d
 foreach(...) {
 
 } while {
 
 }
 ```
The problemm with this syntax is that it looks too much like two consecutive loops: ```d foreach(...) { } while (...) { } ``` Maybe a better syntax might be: ```d foreach(...) { } continue { } ``` So it becomes clear that the second block is part of the loop. T -- Chance favours the prepared mind. -- Louis Pasteur
Feb 13
parent Quirin Schroll <qs.il.paperinik gmail.com> writes:
On Tuesday, 13 February 2024 at 18:19:40 UTC, H. S. Teoh wrote:
 […]
 Maybe a better syntax might be:

 ```d
 foreach(...) {
 } continue {
 }
 ```
This looks like it is target of `continue`, which it (probably) wouldn’t be. I don’t think this feature makes no sense for `foreach` loops; those are high-level, and this is a low-level feature.
Feb 15