Welcome to Atalasoft Community Sign in | Help

F# Range Specification Mistake

I’ve already mentioned how much I dislike F# for loops.  I ran into an issue in the spec that affects loops as well any language construct that uses the range syntax expr1 .. expr2.  The spec for ranges is this:

The default definition of these operators is in Microsoft.FSharp.Core.Operators. The (..) operator generates an IEnumerable<_> for the range of values between the given start (expr1) and finish (expr2) values, using an increment of 1 (as given by Microsoft.FSharp.LanguagePrimitives.GenericOne).

This is great – if you use the following code:

let x = [| 0..7 |];;

you get this:

val x : int [] = [|0; 1; 2; 3; 4; 5; 6; 7|]

which is what you’d expect.  If you do this, on the other hand:

let x = [| 7..0 |];;

you get this:

val x : int [] = [||]

Empty array?  Really?  Well, yes – by the spec they say that they will construct an IEnumerable<_> and use an increment of 1.  In reality, they should use an increment of sign(expr2expr1). This means that if I write the following function:

let f a b =

    for i in a .. b do someOtherFunction i

I will only get predictable results is a < b.  The workaround is to write it this way:

let f a b =

    for i in a .. sign (b – a) .. b do someOtherFunction i

in order to get predictable results.  Note that I do not want to do this:

let f a b =

    for i in (min a b) .. (max a b) do someOtherFunction i

as that will always run low to high and not in the order asked

Range syntax is a nice bit of sugar.  It’s a shame to see it so badly broken.  I imagine that this was brought in from OCAML but the section of the spec (such as it is) that I found doesn’t proscribe the ordering of a range.  I’d be interested in hearing if the F# implementation was accidental or not.  It would certainly lighten the syntax by removing the need for downto in for loops.

Published Wednesday, June 30, 2010 4:10 PM by Steve Hawley

Comments

Wednesday, June 30, 2010 6:36 PM by RickM

# re: F# Range Specification Mistake

While this is a small annoyance, you can correct it yourself easily.  This is one of my favorite things about F# over C#, you almost always have the option to do it yourself if you don't like something that is already implemented.

So, the best solution looks like this:

   let inline (..) a b =

       if a < b then

           seq { for x = a to b do yield x }

       else

           seq { for x = a downto b do yield x }

..and some tests..

   let testUp () =

       printf "testUp: "

       for x in 0 .. 10 do

           printf "%i " x

> testUp();;

testUp: 0 1 2 3 4 5 6 7 8 9 10 val it : unit = ()

   let testDown () =

       printf "testDown: "

       for x in 10 .. 0 do

           printf "%i " x

> testDown();;

testDown: 10 9 8 7 6 5 4 3 2 1 0 val it : unit = ()

I also thought this would be a fun opportunity to show off Seq.unfold.  The following implementation works the same as above:

   let inline (..) a b =

       if a < b then

           Seq.unfold (fun s -> if s <= b then Some(s, s + 1) else None) a

       else

           Seq.unfold (fun s -> if s >= b then Some(s, s - 1) else None) a

Anonymous comments are disabled