www.digitalmars.com         C & C++   DMDScript  

digitalmars.dip.ideas - Action blocks / Blocks with results

reply Quirin Schroll <qs.il.paperinik gmail.com> writes:
An action block is a statement block with a result. It’s 
introduced via `do` and a statement block, followed by a result 
expression that is the result of the action block.

Example:
```d
const x = do { int y = 0; ++y; } y;
assert(x == 1);
```
This initializes `x` to 1. The variable `y` declared in the block 
is in scope for the result expression, but not afterwards. This 
eliminates the need for a yield or return keyword entirely. 
Blocks whose declarations leak out have precedent in `for` loops.

A simple mental model is a `do {…} while(false)` loop with an 
added result.

The `break` keyword can be used to skip the rest of the action 
block and continue with evaluating the result expression.

```d
const x = do { int y = 0; break; ++y; } y;
assert(x == 0);
```

The action block can be labelled by placing an identifier between 
`do` and the opening brace. That allows to break out of any block 
early:
```d
const x = do xInit {
     const y = 0;
     const y0 = do {
         int z;
         break xInit;
     } z;
} y;
```
This jump forward cannot skip the initialization of any variable 
used in the result expression of the action block that’s ended:
```d
const a = do {
     break; // error, skips initialization of b
     int b;
} b;

const x = do xInit {
     const y = do {
         int z;
         break xInit; // error, skips initialization of y;
     } z;
} y;
```
A `break` is conceptually more like `goto`.

The benefits is that it both allows initializations of 
non-mutable variables and allows control-flow statements such as 
`return`, `continue`, and `break` to affect the enclosing 
function. An immediately invoked lambda cannot have the latter 
ones. Another benefit is keeping scopes of local variables small.

It should be guaranteed that the result of a action block is not 
copied to its result. It’s directly constructed into it (for 
initializations) or moved (for assignments).

Maybe: A `ref do` action block yields its result by reference. 
That result cannot be local to the action block, of course, but 
the result could be an lvalue expression that uses local 
variables of the action block as parameters.
Aug 06
next sibling parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
I've been thinking about this a bit.

At the very least the label support can be removed. A label at the end 
of scope + a goto will do the same job but with a more familiar syntax.

Next I'm not convinced that you have the return value right.
A better syntax might be:

```d
Type got = do(Type var) {

};
```

The reason I am suggesting this might be better is due to how its going 
to have to be implemented in the compiler. Using comma expression.

```
ScopeExpression {Comma(VarDeclaration declared, ScopeExpression { ... }, 
declared)}
```

I'm pretty sure there is no such thing as ``ScopeExpression``, so that 
alone is gonna screw this proposal over.

Finally I have a question, why do we need this if you can use a function 
literal in its place?
Aug 06
parent Nick Treleaven <nick geany.org> writes:
On Wednesday, 6 August 2025 at 17:50:53 UTC, Richard (Rikki) 
Andrew Cattermole wrote:
 Finally I have a question, why do we need this if you can use a 
 function literal in its place?
Already answered:
 allows control-flow statements such as return, continue, and 
 break to affect the enclosing function. An immediately invoked 
 lambda cannot have the latter ones.
For break read `break label;` where the label is outside the `do` block. Same goes for `goto label;`.
Aug 06
prev sibling parent Nick Treleaven <nick geany.org> writes:
On Wednesday, 6 August 2025 at 17:20:54 UTC, Quirin Schroll wrote:
 An action block is a statement block with a result. It’s 
 introduced via `do` and a statement block, followed by a result 
 expression that is the result of the action block.

 Example:
 ```d
 const x = do { int y = 0; ++y; } y;
 assert(x == 1);
 ```
 This initializes `x` to 1. The variable `y` declared in the 
 block is in scope for the result expression, but not 
 afterwards. This eliminates the need for a yield or return 
 keyword entirely.
It seems an unnecessary constraint to require declaring a variable (or reusing/assigning to an existing variable) for the result, rather than being able to yield an rvalue. I suggest something like this syntax: ```d const x = do { int y = 0; ++y; out y; }; // 1 const z = do { if (x < 0) out 0; log("x >= 0"); out x + 1; }; // 2 ``` This would be useful as a statement too, to prevent the 'triangle if' pattern: ```d do { if (a) break; b = e1; if (b) break; c = e2; if (c) break; code; }; ``` vs: ```d if (!a) { b = e1; if (!b) { c = e2; if (!c) { code; } } } ```
Aug 06