Skip to content

Conversation

@Happypig375
Copy link
Contributor

@Happypig375 Happypig375 commented Jul 23, 2025

@Happypig375
Copy link
Contributor Author

@T-Gro Review wanted

@T-Gro
Copy link
Contributor

T-Gro commented Aug 4, 2025

They should be treated both as valid, I don't think there is a big difference.

@Happypig375
Copy link
Contributor Author

@T-Gro You mean to treat -> the same as do in the context of fold loops?

@Happypig375
Copy link
Contributor Author

But treating -> the same as do for value-returning version would conflict with the existing meaning of do yield in CEs.

@omcnoe
Copy link

omcnoe commented Aug 14, 2025

As a user of F#, I'd be concerned with the fact that 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 (final accumulator state).

It seems a bit surprising that the with keyword & accumulator state changes the type of the whole for expression.

It might be a more clear design if these two differences could be both represented separately in some way:

  • traditional iter-style loops that are always unit VS loops that evaluate to an actual list of values
  • loops that thread accumulator state VS loops that do not

Maybe it's better to allow the programmer to opt in to either decision independently rather than force-bundling both concepts together? Use the with keyword to define that loop threads an accumulator, and use some other syntax (maybe with yield somewhere to define that a loop is value returning?

@Happypig375
Copy link
Contributor Author

@omcnoe If you really think about it, the traditional for loop just has implicit with state = (), i.e. accumulates to unit. If you change the accumulation, you also change the loop value. They aren't actually independent.

@Tarmil
Copy link
Contributor

Tarmil commented Aug 14, 2025

@Happypig375 More exactly, the traditional for loop has an implicit with () = (), ie it accumulates () and doesn't bind any variable in the body of the loop.

@charlesroddie
Copy link
Contributor

@charlesroddie It's an RFC for a non-approved suggestion. The README of fslang-suggestions explicitly states that doing this and starting a prototype implementation can help convince.

This is true and has been there a long time but I haven't seen this done. It doesn't make any sense to me because going into technical details is not important for viewing reviewing a high-level suggestion, unless the suggestion itself is highly technical in nature (e.g. may not work unless various technical aspects align) or has specific technical uncertainties that are important for evaluating it. These are extremely rare. I think we should qualify the readme with that info.

In this case, what is this RFC supposed to show? Are there technical questions that are uncertain from the suggestion that it resolves that are essential for evaluating the suggestion?

@Happypig375
Copy link
Contributor Author

@charlesroddie The original suggestion reads as this

let result =
    for s with t in ts do
        s + t

into

let result =
    ts
    |> #Collection.fold (fun s t -> s + t) s

Obviously there are a lot of questions surrounding this - what is #Collection? Why s with t in ts? It looks weird, relies on magic module searching, and breaks scoping principles. Meanwhile, the justification of doing this is also unclear. The original suggestion without further improvements is highly likely to be rejected.

After careful design as done in this RFC, the result now becomes

let result =
    for t in ts with s = 0 do
        s + t

into

let result =
    let mutable accum = 0
    for t in ts do
        let s = accum
        accum <- s + t
    accum

All the design choices and justifications are to be included in this RFC for a comprehensive evaluation on the validity of this suggestion, being concrete trumps being ambiguous. This is why making an RFC can also help convince approval.

Also, I don't get why you are complaining about procedures instead of the actual suggestion - it seems not to be constructive behaviour especially when paired with the 👎 reaction on the top post, as seen not just in this PR, but also in other issues, where the 👎 reaction persists despite a proper reply.

@Happypig375
Copy link
Contributor Author

@omcnoe See if 23a218c explains it well

@Martin521
Copy link
Contributor

Also, I don't get why you are complaining about procedures instead of the actual suggestion - it seems not to be constructive behaviour especially when paired with the 👎 reaction on the top post, as seen not just in this PR, but also in other issues, where the 👎 reaction persists despite a proper reply.

I do think that the language design process makes sense and I therefore believe it is ok if somebody points to it.
Also, there can be many reason to not favor the addition of a feature even after "proper replies".
My own opinion in this case: The feature is somehow cool, but I still don't want it in F#, based on my perception of the cost-benefit ratio. And because I believe that for the future of F# it is much more important to focus on a) open issues that bother the average user and b) on maintainability of the compiler.

@krauthaufen
Copy link

In my opinion this construct would make it more complex for newcomers to understand code, intransparent about what is actually executed underneath (Seq.fold, List.fold, a for loop with a mutable variable?) and I don't see how it makes code shorter or easier to read. A for loop with a mutable variable is a few characters longer and familiar to most people. Folds are also more transparent in my opinion and can be used in pipe-chains.
I'm just concerned about F#'s simplicity being eaten up by these corner-case specializations.

@reinux
Copy link

reinux commented Aug 16, 2025

@Happypig375 did establish that folds aren't an edge case by any stretch -- in fact, it's the third most common HOF after map and iter, both of which can be expressed as list/seq comprehensions and bare for loops respectively, and also can't be used in a pipe when looped. fold is used 90% as often as iter and almost twice as often as sum, the next most common HOF.

Granted, map and especially iter are used far more often than we see because they're already written as loops but, folds are certainly used a lot in elm-style programs and simulations, as you often need to update the model over a sequence. Given Zipf's Law, I'd wager that iter and bare for loops in total are used half as often as map and list comprehensions combined, and fold is used a third as often -- which is still quite a lot.

To me, the main downside of using a mutable is that, especially as beginners, we're being encouraged to use mutable as sparingly as possible moreso for the sake of discipline than to avoid any immediate danger of juggling state in a brief loop. And then turning around and instructing them to use mutables as an alternative for something as common as folds sends a mixed message -- something I still remember being a head-scratcher 15+ years ago when I first started with functional programming in F#. It greatly simplifies tutorials and documentation if we don't need to make this concession.

Likewise, the downside of folds, especially for beginners, is that they require memorizing a lot of seemingly arbitrarily ordered arguments: ('S -> 'T -> 'S) -> 'S -> 'T seq -> 'S is pretty overwhelming.

Virtually the only situation you'd ever reach for the ||> operator is to alleviate the problem with folds obscuring the state and xs args behind a giant function argument with potentially several nested brackets. Although it's a dead simple one-liner in the core library as opposed to a language feature as in this proposal, as far as the user is concerned, a new operator is always new syntax. And it flips the argument order and switches from curried to tupled.

Finally, this is a relatively minor point, but if you're trying to draft up a loop or HOF application for a complicated algorithm, you might first try a fold and find that it isn't sufficient. I think both beginners and advanced users alike have this experience routinely. In those situations, refactoring the function argument to an HOF is not as intuitive as adding or removing things to/from a loop.

@krauthaufen
Copy link

Just to clarify, I didn‘t mean to say that folds are not common, I meant to say that the places where you actually benefit from this syntax (meaning shorter or more readable code) seem pretty far fetched to me. The „don‘t use mutables“ argument is actually pretty bad since mutable variables inside a function body don’t harm anyone. Global mutables and mutable objects kill functional programming, not some accumulator variable.

@reinux
Copy link

reinux commented Aug 16, 2025

I wouldn't argue that it's shorter. F# doesn't really go out of its way to make code shorter. It may or may not be more readable to an experienced user either.

The main point is that it can help with adoption if users don't have to go through the whole journey of "Wow, folds are pretty overwhelming, can't I just use a loop? Oh I guess I can but can I do it without using a mutable since I'm trying to practice immutability right now? Oh okay I guess I can use ||> but it's not much of an improvement... I guess I'll stick with folds"

@krauthaufen
Copy link

I don’t see how this should be beginner friendly, you use an imperative construct (a loop) which in its body returns a value which is initialized by using with s = 0 (a keyword suggesting an immutable declaration) where in the loop-body it‘s actually replacing its value with the new one provided and in the end your entire loop returns the overall value. I don‘t claim to have a Solution for your syntactic issues with fold but maybe a simple CE builder for this would also do the job?

@reinux
Copy link

reinux commented Aug 16, 2025

Although it's in a different form, intuitively, it feels a lot like the for loops in C-style languages, with the initialization, the condition and the increment. It's been a while, but I don't remember C for loops ever confusing me. I think it's a pretty big improvement in beginner friendliness. I find that formalized imperative styles are often a good compromise between safety and ease of learning, which is often why list comprehensions are preferred over maps.

The main problem with CEs, which has been pointed out in the RFC, is that they're notoriously difficult to debug.

@charlesroddie
Copy link
Contributor

All the design choices and justifications are to be included in this RFC for a comprehensive evaluation on the validity of this suggestion, being concrete trumps being ambiguous. This is why making an RFC can also help convince approval.
Also, I don't get why you are complaining about procedures instead of the actual suggestion

Comprehensive evaluation doesn't make sense before summary evaluation. High level first then details and that has always been the approach. If an unambiguous summary isn't possible that should be explained in the summary and some attempt made to communicate it. The concrete problems created is that time is wasted reviewing details - it's other people's time but sometimes reviews are requested and people may feel they have to review the details. My role is only to keep an eye out that any technical discussion here doesn't bypass a high level process of community review of the language suggestion, resulting in features getting into F# which are fully technically specified but which are not good overall additions to the language.

@T-Gro
Copy link
Contributor

T-Gro commented Nov 26, 2025

Thank you for putting together this detailed RFC. The level of detail provided was genuinely helpful in understanding both the motivation behind the proposal and the various technical options considered. It's clear that significant thought and effort went into this document.

After careful consideration, I've decided to close this PR for now. The reasoning is captured in the discussion thread, where I've marked the suggestion as "Probably not".

As noted there, this doesn't preclude revisiting the idea in the future should circumstances change—whether through new compelling use cases, shifts in the ecosystem, or renewed community interest and consensus.

Thank you again for your contribution to the F# language design process.

@T-Gro T-Gro closed this Nov 26, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.