digitalmars.dip.ideas - Action blocks / Blocks with results
- Quirin Schroll (59/59) Aug 06 An action block is a statement block with a result. It’s
- Richard (Rikki) Andrew Cattermole (19/19) Aug 06 I've been thinking about this a bit.
- Nick Treleaven (5/10) Aug 06 Already answered:
- Nick Treleaven (37/49) Aug 06 It seems an unnecessary constraint to require declaring a
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
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
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
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