Effectively final loop counter (was: Are templated string embedded expressions "method parameters" or "lambdas"?)

John Rose john.r.rose at oracle.com
Tue Nov 2 03:30:07 UTC 2021


On Oct 30, 2021, at 9:30 PM, Tagir Valeev <amaembo at gmail.com<mailto:amaembo at gmail.com>> wrote:

This is a somewhat separate topic but I would be glad to see some
improvement here with respect to good old for loops, as I often saw
copying to a new variable for the sake of lambda capture inside the
loop.

Like if a variable is declared at the for loop initializer and it's
never modified inside the `for` loop body, then let's assume that
before body entry, a fresh variable is created with the same name and
assigned to the original variable shadowing the original variable.
This way, the counter variable of the classic counting loop will be
considered as effectively final inside the loop body (but not inside
the condition and update expressions). This will make

for (int i=0; i<n; i++) {...}
close to
for (int i: IntStream.range(0, n).toArray()) {...}
But without performance overhead.

This change is backward-compatible.

With best regards,
Tagir Valeev

+1; I’ve wanted this at various points.

It could be formalized like this:

1. Allow old-for loop variables to be declared final.

2. As a very special rule, allow side-effects to such final variables, in the for-loop header (anywhere).

3. When such side-effects are detected (in the for-loop header), split the variable into an outer binding, not final, and an inner binding, final and a copy of each successful value of the outer.

4. All occurrences of the name in the for-header refer to the outer/mutable variable.

5. All occurrences of the name in the for-body refer to the inner/final variable.

6. For debuggers, maybe give the inner/final variable a different name, say, i$final.

This amounts to a desugaring:

for (final int i = 0; i < a.length; i++) { foo(i); }

=desugar=>

for (int i = 0; i < a.length; i++)
  { __Synthetic final var i$final = i; foo(i); }

Oddities:

- The split name i$inner shows up in the debugger, maybe.
- Or else there are confusingly duplicate declarations in the debugger.
- You can only get lambda-capture of ‘i’ in the loop body proper, not in the loop header.
- The transform applies to individual variables; with ‘for (int i,j…)…’ the transform may be applied independently to i and j.

This design admits an “effectively final” transformation, which would get what you are requesting directly.

for (int i = 0; i < a.length; i++) { foo( () -> i ); }

=effectively final=>

for (final int i = 0; i < a.length; i++) { foo( () -> i ); }

=desugar=>

for (final int i = 0; i < a.length; i++)
  { __Synthetic final var i$final = i; foo( () -> i$final ); }

This can be made to work for multiple variables also, and has reasonable effects, although it stretches for-loops a bit.

for (final int i = 0, j = a.length; i < j; i++) { foo( () -> i ); }

=desugar=>

for ({ int i = 0; final int j = a.length; }; i < j; i++)
 { __Synthetic final var i$final = i;  foo( () -> i$final ); }

OR ELSE (not as good):

for (int i = 0, j = a.length; i < j; i++) {
  __Synthetic final var i$final = i;
  __Synthetic final var j$final = j;  //unused
  foo( () -> i$final ); }

The latter approach is not as good because, although it makes for
a simpler desugaring, it makes the de-finaling of i “viral” to j.
The change of j in the loop header, to a mysteriously non-final
variable, could be surprising.

By contrast, the appearance of “i++” is unambiguously and
aggressively clear, that the programmer intends to change
i, even at the same time as declaring the programmer’s
desire for a final i.  This is usually a contradiction, but
can be interpreted usefully and consistently in the setting
of a loop:  It’s final *only in each iteration of the body*.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20211102/bf041e05/attachment-0001.htm>


More information about the amber-spec-experts mailing list