Idea: Qualified jumps
Quirin Schroll
qs.il.paperinik at gmail.com
Mon Mar 14 17:33:29 UTC 2022
#### Idea
Imagine you have a loop that does multiple things slightly
differently. You think about refactoring those similar parts into
a single function that takes a few parameters and could even have
a nice, descriplive name. And you can’t — because there's a pesky
`return` or `break` statement that just wouldn't work when put
into another function. How would it even work anyways? Well, how
would it? It couldn’t ever work in a global-scope function,
that’s for sure, but it could — in principle — work in a nested
function or a lambda under some reasonable conditions.
I've thought some time about the syntax, and `parnet.return
value` or `parent.break label` (i.e. ⟨qualified-name⟩.⟨statement⟩
⟨args⟩) felt the best, so I'm using. It is borrowed from
`object.BaseClass.method` syntax.
#### Example
Show, don’t tell:
```D
int parent(int[] xss)
{
loop:
foreach (xs; xss)
{
foreach (x; xs)
{
void child(int y)
{
if (isPrime(y))
parent.break loop;
else if (y == 0xF00D)
parent.return 1234;
else if (y > 1_000_000)
parent.goto veryBig;
}
child(x);
higherOrderFunc(&child, x);
}
}
return 0;
veryBig:
writeln("wow!");
return 1;
}
```
The function `child` must be declared in the loop. Declare it
before and it does not see the label `loop` (you could object to
this). Declare it after and its name is not avaliable inside the
loop (this has always been the case).
The reasonable conditions are: If you ever take the address of
that function, it must be assigned to a `scope` variable.
Otherwiese when the function is called, the stack frame it
referred to might not exist anymore. It cannot be returned by the
parent function. And it cannot be `static`.
The only case without ⟨args⟩ is `return` in a `void` context.
`break` and `continue` must use the labelled form.
#### The Juicy Part
If we go further and deprecate lambdas that start with a brace
(i.e. `auto lam = {}` lambdas, not `auto lam = (){}` lambdas), we
could change their semtantics: Every `return`, `break`,
`continue`, and `goto` statement would be implicitly a
`parent.return`, `parent.break` etc. targeting the nearest
control-flow structure.
Together with some proposal to omit parentheses when the only (or
last) argument is such a special lambda, it would allow for
custom control-flow statements.
```D
int parent() {
myControlFlowStmt(args) { lambda content; return 0; }
}
```
is lowered to
```D
int parent() {
myControlFlowStmt(args, () { lambda content; parent.return 0;
});
}
```
The lowering for `foreach` does something similar, but there
isn't really a way to utilize that functionality outside of it.
#### Corner Cases
If you have multiple nested functions, search without a leading
dot is inside-out and outside-in with a leading dot:
```D
void parent() // 1
{
void parent() // 2
{
void parent() // 3
{
void child()
{
return; // exits child
child.return; // (same) exits child
parent.return; // exits 3
parent.parent.return; // exits 2
parent.parent.parent.return; // exits 1
.parent.return; // exits 1
.parent.parent.return; // exits 2
.parent.parent.parent.return; // exits 3
.child.return; // error (child is not at global
scope)
.parent.child.return; // error (child not
directly under global .parent)
}
}
}
}
```
If you name the parents differently, of course you don't need all
these; `parent2.return` will suffice. Also note that nesting
functions with the same name is legal in current D.
#### Who Is This For?
This looks like it asks for spaghetti code. But it's rather for
being able to express something one cannot otherwise (easily) do.
You would want that if you’re caught between code duplication and
optimization.
More information about the Digitalmars-d
mailing list