Skip to content

Commit 23a218c

Browse files
authored
Looks too similar to a regular loop?
1 parent 0e94e47 commit 23a218c

File tree

1 file changed

+52
-2
lines changed

1 file changed

+52
-2
lines changed

RFCs/FS-1152-For-loop-with-accumulation.md

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
To do:
2+
- Justification of the fundamentality of folds by counting usage - why improve folds specifically?
3+
- More on dismissing CE alternative
4+
- Looks too similar to a for loop?
5+
16
# F# RFC FS-1152 - Fold loops
27

38
The design suggestion ["for-with" syntactic sugar for folds](https://github.com/fsharp/fslang-suggestions/issues/1362) has not yet been marked "approved in principle".
@@ -442,6 +447,51 @@ for <pattern_enumeration_item> in <expression_sequence> with <pattern_accumulato
442447

443448
This syntax analysis reveals an insight that the fold loop behaves identically to the `for` loop `with` accumulation. Note that the design process started from `fold` functions, then used an overload on `for`, then naturally evolved to an extension on existing `for` loops. We did not force the idea of folding onto an extension of existing `for` loops, instead maintaining the orthogonality between fold loops and existing usages of the `for` keyword. This shows the design of an extension on the `for` loop is coincidental from overloading the `for` keyword, instead of tacking on the meaning of `fold` as with an extension of the `for` loop as the starting point - an important semantic difference.
444449

450+
## Doesn't this look too similar to a regular `for` loop despite the differences?
451+
452+
tl;dr Yes, and this is intentional - it reveals a fundamental unification.
453+
454+
On the surface, this proposal is overloading the `for` keyword into two forms, one that evaluates to unit and another new form that actually evaluates to a real value, which is final accumulator state, while threading the accumulator each iteration. It seems a bit surprising that the `with` keyword and accumulator state changes the type of the whole for expression. Adding a `with` clause might seem like a superficial change to regular `for` loops.
455+
456+
However, this syntax highlights a profound realization: traditional `for` loops are actually a specialized case of fold loops with a unit accumulator.
457+
458+
```fsharp
459+
// Traditional for loop
460+
for x in xs do
461+
printfn $"{x}"
462+
463+
// Is equivalent to:
464+
for x in xs with _ = () do
465+
printfn $"{x}" // Body must return unit
466+
```
467+
468+
where the accumulator binding is discarded `_`, with its initial value set to the unit value `()`, and the new accumulator value is the value of the loop body which can only be the unit value `()`.
469+
470+
This is consistent in two ways:
471+
- For discards
472+
```fsharp
473+
// Regular expression in do position
474+
do 1 // Warning: result implicitly ignored
475+
476+
// Traditional for loop
477+
for x in xs do 1 // Warning: result implicitly ignored
478+
479+
// Unit-accumulator fold loop
480+
for x in xs with () = () do 1 // Same warning
481+
```
482+
483+
- For types
484+
```fsharp
485+
let y = 1 // y : int
486+
487+
// Non-unit fold loop
488+
for x in xs with y = 1 do
489+
y + x // accumulator : int
490+
```
491+
The `with` clause changes the loop body from an implicit `unit -> unit` (traditional loop) to arbitrary `'State -> 'State` (fold loop). The presence of the `with` clause doesn't just add an accumulator - it adds flexibility to the loop from a side-effect-only construct (`unit -> unit`) to a value-producing operation (`'State -> 'State`).
492+
493+
This explains why the loop's return type changes: it's not an arbitrary overload but a natural consequence of the accumulator type. The syntactic similarity is actually a strength - it reveals the deep connection between iteration and accumulation that was previously obscured.
494+
445495
## Are there better alternatives to `with` keyword?
446496

447497
tl;dr No.
@@ -468,7 +518,7 @@ for x in xs mutable s = init do ... // "mutable" isn't used without "let" an
468518

469519
## Can the accumulator initializer be simplified?
470520

471-
tl;dr No.
521+
tl;dr No, it's technically infeasible.
472522

473523
Some may also suggest that re-declaring the accumulator initial state is redundant in the case of folding over different sequences one after another.
474524
```fs
@@ -478,7 +528,7 @@ However, a direct abbreviation from `effects, model = effects, model` to `effect
478528

479529
## Can it be used with computation expressions?
480530

481-
**tl;dr No.** The fold loop body cannot contain computation expression operations, as it's designed as a pure accumulation expression. This maintains consistency with how subexpressions work throughout F#.
531+
tl;dr No. The fold loop body cannot contain computation expression operations, as it's designed as a pure accumulation expression. This maintains consistency with how subexpressions work throughout F#.
482532

483533
### Why CE operations are excluded
484534
```fsharp

0 commit comments

Comments
 (0)