Open baronfel opened 8 years ago
While functional programming purists could say that strengthening imperative programming features in the language is not desirable, I think that both the break and continue keywords are long-awaited additions to the F# functional-imperative language for pragmatic reasons (migration of codebases from Java, C++ or another imperative language, friendliness of the language syntax to programmers with imperative programming background, i.e. the majority of new language users). The break and continue constructs would be useful in two forms: labeled and unlabeled, like in Java, the TIOBE TOP1 language. Labels attached to loops have to be declared in the Java style: a label name followed by a colon. I believe it has the potential to positively affect the F# popularity. Please see details of Java syntax at https://docs.oracle.com/javase/tutorial/java/nutsandbolts/branch.html
I agree, but for a different reason: performance (break and continue may very well be faster).
@DemiMarie I think this would need substanciation if we could provide samples highlighting the issue.
From discussions with seasoned F# developers, it seems recursive function or while loop with a boolean condition would have pretty similar cost.
The feature was mentionned by @migueldeicaza : https://www.infoq.com/articles/virtual-panel-dotnet-future
For F#, my request is simple: I am a man that is too attached to his old ways of writing loops. I want my loops to include support for break and continue. I know this is heresy, but that is what I desire the most :-)
Well, going on the road of performance, and not introducing many arbitrary constructs, what about goto
and labels? break
and continue
are limited to the closest loop which is not always what you want.
So far I haven't missed those constructs, and I think they are not obvious to introduce gracefully in expression based F#.
It would be nice to have a summary of downvoters motives.
I am against this. Use of "break" (except in "case"), "continue", "goto" and "return" in C# can make it hard to understand the meaning, especially in large methods where use of these keywords can be hard to spot.
You should be able to quickly make quite a few assumptions just by taking a glance at the structure of the code. These assumptions easily break when any of these mechanisms are allowed. For that reason alone they should all be banned in F#.
Although simple moderate sensible use of "break" (and "return") is very popular and de facto standard pattern in C#, and definitely makes code shorter and more readable, there is no need to have this in F#, since equally simple functional constructs exist. Translating from C# to F# is very simple in these cases.
C# source can be rewritten to not use them, and then easily be ported to F#. If the C# code is hard to rewrite, then it just shows the code had low quality to begin with, and it justifies why it should not be so in F#.
I have translated tens of thousands of lines of C# to F#. The biggest stumbling blocks by far is with "continue", heavy use of "break" in odd places in loops, and not least "goto". Even if these were available in F#, it would be even more important for me to refactor them away as quickly as possible in order to take advantage of the language. In hard cases, that refactoring is easier done in C# anyway.
Although F# is a multi-paradigm language, it is also a functional first language. These mechanisms are impure even in the imperative domain. They would influence even the functional constructs in a negative way, because they make it harder to reason about the code in total.
I think this, as well as adding normal imperative "return" would be a great change to F#. Right now in F# there are a few alternatives:
Option.map
asyncMaybe
, for example. That is not elegant, it is ad-hoc and crazy.)
ParseResult
and HttpRequestResult
)Some people say let rec
and small functions is an alternative, but in that case you are at best shuffling the problem around. It is like telling someone callback hell is fine, because you can just split the logic up into more functions.
The following code may help:
module Range =
let inline (|Range|) (x: Range) = (x.Start.Value, x.End.Value)
let inline range a b = Range(Index a, Index b)
open Range
type LoopBuilder() =
member inline __.For(s: seq<_>, f) =
use s = s.GetEnumerator()
let mutable result = ValueNone
while s.MoveNext() &&
( result <- f s.Current
result.IsNone
) do ()
result
member inline __.For(Range(x, y), f) =
let mutable result = ValueNone
let mutable i = x
while i < y do
result <- f i
i <- if result.IsNone then
i + 1
else
y
result
member inline __.For(s: IReadOnlyList<'t>, f) =
let len = s.Count
let mutable result = ValueNone
let mutable i = 0
while i < len do
result <- f s.[i]
i <- if result.IsNone then
i + 1
else
len
result
member inline __.Return x = ValueSome x
member inline __.Zero() = ValueNone
member inline __.Yield x = x
member inline __.Combine(x, f) =
match x with
| ValueSome x -> x
| ValueNone -> f()
member inline __.Delay f = f
member inline __.Run f = f()
let loop = LoopBuilder()
usage:
// `yield` for default value, used it, the true type value return.
let foo2 (arr: int[]) = loop {
for x in arr do
for y in arr do
if x * y > 10000 then
return x + y
yield 0
}
// without `yield`, it return a `voption` value
let foo3 (arr: _ List) = loop {
for x in arr do
if x > 10000 then
return x
}
**// It's hard to say, it don't support ` for x in 0 .. 10000 do`, when you write that, it call the `seq` overload.
// can somebody tell me how?**
let foo4 (xs: Range) = loop {
for x in xs do
if x > 10000 then
return x
}
This code looks ugly. I did thousands of experiments, it make the cleanest result:
public static FSharpValueOption<int> foo3(List<int> arr)
{
int count = ((IReadOnlyCollection<int>)arr).Count;
FSharpValueOption<int> result = FSharpValueOption<int>.ValueNone;
for (int num = 0; num < count; num = ((result.Tag != 0) ? count : (num + 1)))
{
result = f@38-3(((IReadOnlyList<int>)arr)[num]);
}
return result;
}
internal static FSharpValueOption<int> f@38-3(int _arg1)
{
if (_arg1 > 10000)
{
return FSharpValueOption<int>.NewValueSome(_arg1);
}
return FSharpValueOption<int>.ValueNone;
}
I do not agree that the calculation expression must be inefficient.
However, I would rather if F# added the break return
syntax.
@greatim my poorman's break is always async{ ... }
without actual async code :p
@greatim my poorman's break is always
async{ ... }
without actual async code :p Oh no! It's hard to use, and packed with several layers of function.
@yatli Please give sample code
simply:
async {
while true do
if foo then return
}
simply:
async { while true do if foo then return }
It doesn't compile
Uh oh. But why? I thought I understand CE. 😅😅
Uh oh. But why? I thought I understand CE. 😅😅
hmmm, I can see why now. I fixed it for this:
let foo x =
async {
while true do
if !x then return ()
}
but the loop won't be breaked.
@greatim yeah that's my confusion -- why doesn't it stop at return? What does it even mean to return multiple times from async? That the async CE can be run multiple times, statefully, like IAsyncEnumerable?
What does it even mean to return multiple times from async?
I don't know what it does.
But last time I told my friend that "using async
can break the loop".
He did it, and hit my face on the next day.
I think as a general rule we should keep posts in these open repos (fsharp/fslang-suggestion, dotnet/fsharp, etc) focused on the issue, so that we won't have to waddle through unrelated stuff repeatedly. In my experience one tends to read or skim through the whole thread from start to end several times. I suggest you sort out side issues like this in one of the channels in F# Slack, unless you feel it's actually relevant to the issue.
I am against this. Use of "break" (except in "case"), "continue", "goto" and "return" in C# can make it hard to understand the meaning, especially in large methods where use of these keywords can be hard to spot.
IDEs should have a separate category for coloring these keywords. I would use red.
Submitted by Vladimir on 1/5/2015 12:00:00 AM
12 votes on UserVoice prior to migration
It very often looks better than recursion, and it would be wonderful to have this possibility
Original UserVoice Submission Archived Uservoice Comments