Thread overview
[Language construct idea] The for loop with prepare step
6 days ago
Quirin Schroll
6 days ago
Quirin Schroll
February 12

Extend for to for (init; prepare; condition; increment) body.

The step prepare is executed before each check. Ignoring scope, the above is lowered to:

    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.
February 12

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?

February 12
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
February 13
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.
February 13

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:

import core.stdc.stdio;
for (int c; (c = fgetc(fp)) != EOF; ) { }

once could do:

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.

February 13

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:

do
{
    …
}
while (cond)
{
    …
}

It’s a lot more structured than:

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:

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/nassishneidermann.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:

foreach(x; xs)
{
    …
}
while
{
    …
}

This is my attempt to express it in terms of a while loop and range primitives:

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.

February 13
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
6 days ago

On Tuesday, 13 February 2024 at 18:19:40 UTC, H. S. Teoh wrote:

>

[…]
Maybe a better syntax might be:

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.

6 days ago

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:

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