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?
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).
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
Another possible workaround:
[1L..100L] |> List.iter (fun i -> printfn "%i" i)