Why are simple for loop expressions restricted to

2019-02-21 12:11发布

问题:

According to the F# spec (see §6.5.7), simple for loops are bounded by integer (int aka int32 aka System.Int32) limits start and stop, e.g.

for i = start to stop do
    // do sth.

I wonder why the iteration bounds for this type of for loop are required to be int32. Why not allow uint32? int64? bigint?

I'm aware that sequence iteration expressions (for ... in ...) can iterate over arbitrary sequences; that however requires allocating an iterator and calling MoveNext and Current and what not and can thus be considerably less efficient than a plain loop could be (increment counter, compare, conditonal jump). To avoid that, you are stuck with using while and a manually incrementing loop counters...

Strangely enough, F# does allow non-int32 loop bounds, if the for expression is wrapped in a sequence expression, e.g.

seq { for i = 0I to 10I do
        printfn "%A" i }

So, I guess the question is: Is there a particular reason for only allowing int32 for loops? And why does this restriction not apply to for loops wrapped in seq expressions?

回答1:

I'm not sure why F# does not allow int64 ranges. It sounds like a useful feature... (but I can understand that int is the standard type for this in C# and perhaps F# tries to follow this pattern).

As for the workarounds, it is worth adding that you can also write inline higher-order function:

let inline longFor low high f = 
  let rec loop n =
    if n < high then f n; loop (n + 1L)
  loop low

...and then you can express for loops over int64 ranges in a fairly succinct way:

longFor 1L 100L (fun n -> 
  <whatever> )

I did a couple of experiments and it seems that the F# compiler is able to optimize this fairly decently (the lambda function is inlined and the tail-recursive loop function is turned into a while loop). I do not think this is guaranteed so you may need to check this by hand in high-performance code, but it seems to work fine for simpler examples.

There is only one disadvantage - you won't be able to use local mutable variables (let mutable) because these cannot be captured by a lambda function. So there may be additional cost with indirect ref cells (but I'm not sure how big problem this is).



回答2:

If you want to keep the for-loop, there's a very simple work-around using the for...in loop with a sequence range operator:

for i in 0I .. 10I do
    printfn "%A" i

The range operator will accept any integer of any size as long as both types match. For example, the following will not compile:

for i in 0 .. 10I do
    printfn "%A" i


回答3:

Another possible workaround:

[1L..100L] |> List.iter (fun i -> printfn "%i" i)