Why are simple for loop expressions restricted to integer ranges?
Asked Answered
N

3

16

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?

Niles answered 16/4, 2013 at 19:53 Comment(3)
In general, the .NET framework uses int as a general-purpose integer, including its use in all sorts of numerical indexing scenarios. Example: msdn.microsoft.com/en-us/library/…. Counter-example: msdn.microsoft.com/en-us/library/…, which uses a long.Tabatha
F# encourages functional programming and therefore stops short of full imperative support (e.g., the lack of break/return). Within a computation expression for is desugared to a method call, which isn't inherently imperative like a loop, and therefore doesn't have the same limits. I can understand the mystery though. +1Zawde
@Zawde +1 "Within a computation expression for is desugared to a method call". I see; actually the code generated for seq { for .. to .. do .. } and seq { for .. in .. do } is largely identical, both are transformed to a GeneratedSequenceBase<_> enumerator.Niles
C
7

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).

Chang answered 17/4, 2013 at 0:34 Comment(1)
+1 I also recently experimented with how the F# compilers handles inlined higher-order functions and I was quite surprised (and pleased) to see that it even inlines lambdas at the call site, at least as of F# 3.0. I now use an approach very similar to your suggestion, but with an imperative while loop instead of tail-recursion (so I can use a plain mutable instead of a ref cell). Seems to work fine so far.Niles
W
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
Wolk answered 22/1, 2014 at 19:50 Comment(2)
Thanks, but I'm aware of that; read the second paragraph of my question.Niles
Oops... missed that. Was coming from another question...Wolk
C
0

Another possible workaround:

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

Continue answered 17/4, 2013 at 11:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.