Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

for…in…while #1400

Open
6 tasks done
IS4Code opened this issue Jan 12, 2025 · 5 comments
Open
6 tasks done

for…in…while #1400

IS4Code opened this issue Jan 12, 2025 · 5 comments

Comments

@IS4Code
Copy link

IS4Code commented Jan 12, 2025

I propose we have a new construct combining while and for…in that iterates over a collection while a condition is maintained, i.e. for…in…while:

for pattern in enumerable-expression while test-expression do
    body-expression

This construct shares the behaviour of both for…in and while, i.e. it evaluates test-expression upon each entry into body-expression, and once test-expression is false, it stops the iteration.

test-expression may use any variables declared by pattern.

The existing way of approaching this problem in F# is to use Seq.takeWhile or to lower the code manually to use GetEnumerator and while.

Pros and Cons

The advantages of making this adjustment to F# are simplicity of use of for with breaking conditions in situations where a more traditional rewriting into purely functional code is unwieldy or impossible, for example when byref-like types are involved, as well as other enumerable types in .NET that do not implement IEnumerable. It is also better to have a dedicated construct for this than to use Seq.takeWhile in case the condition is not pure (e.g. it depends on body-expression). In the case of manual lowering using GetEnumerator, it is prone to mistakes (for example if one forgets to call Dispose), and misses out on optimizations that F# performs for for for ranges, lists, arrays, or spans.

The disadvantages of making this adjustment to F# are having yet another "imperative" construct that people may pick instead of expressing the intention in a more idiomatic way. However, sometimes there are no better options (e.g. spans) and the same can be argued against while too.

Extra information

Estimated cost (XS, S, M, L, XL, XXL): M

Related suggestions: #381 (another approach using break)

Affidavit

  • This is not a question (e.g. like one you might ask on StackOverflow) and I have searched StackOverflow for discussions of this issue

  • This is a language change and not purely a tooling change (e.g. compiler bug, editor support, warning/error messages, new warning, non-breaking optimisation) belonging to the compiler and tooling repository

  • This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it

  • I have searched both open and closed suggestions on this site and believe this is not a duplicate

  • This is not a breaking change to the F# language design

  • I or my company would be willing to help implement and/or test this

For Readers

If you would like to see this issue implemented, please click the 👍 emoji on this issue. These counts are used to generally order the suggestions by engagement.

@En3Tho
Copy link

En3Tho commented Jan 13, 2025

While I understand that you want to preserve somewhat "functional" approach I think this will be more limiting vs simply introducing "break" and "return" into the language.

I do miss them myself quite often. I'm not a fan of using recursion where it is not a recursive thing in nature but a workaround. And when used in such way it often makes code worse both performance and readability wise.

@csharper2010
Copy link

I think the benefit over using Seq.takeWhile is quite limited. No need to put that into the language.

@Tarmil
Copy link

Tarmil commented Jan 13, 2025

This makes me miss iter in Common Lisp 😄 where you can do

(iter (for pattern in enumerable-expression)
      (while test-expression)
      body-expression)

That being said, I agree with @En3Tho: this is nice but ultimately a less powerful construct than break.

@IS4Code
Copy link
Author

IS4Code commented Jan 13, 2025

I understand the concerns. If break/continue have a better chance of entering the language, then I am all for them, but from what I have seen there is some opposition, whereas here I don't think those arguments apply as this is a more functional-friendly approach. This might also work a bit better with computation expressions.

Without a way to cleanly exit a for loop, I am stuck with sub-par code in any case ‒ either it does not work with byref-structs, misses out on optimizations for the specific types, or if I go around that and use exceptions instead of break, it gets very inefficient.

That being said, if for pattern in enumerable-expression |> Seq.takeWhile (fun _ -> test-expression) do works for all of the same cases and with the exact same performance like normal for, then that could work as well. However I doubt that will happen, since this also needs to work in cases when the sequence does not implement IEnumerable.

@brianrourkeboll
Copy link

brianrourkeboll commented Jan 17, 2025

For the specific scenario of byref-like types, I think the "allows ref struct" feature, if we ever support that, would mean that you could write the equivalent of Span.takeWhile predicate span. So if that is on the table, then I don't think it would be worth adding this special syntax.

For non-byref-like types, you could use a computation expression if you wanted...

Source

Example 1

let chars = "asdf lol"
whilst (not << Char.IsWhiteSpace) { for c in chars do printfn $"{c}" }

Example 2

let chars = "asdf lol"
breakable { for c in chars, not << Char.IsWhiteSpace do printfn $"{c}" }

(You could probably write one that used a custom operation to specify the predicate as well.)

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

No branches or pull requests

5 participants