em70
em70

Reputation: 6081

Ranges A to B where A > B in F#

I've just found something I'd call a quirk in F# and would like to know whether it's by design or by mistake and if it's by design, why is it so... If you write any range expression where the first term is greater than the second term the returned sequence is empty. A look at reflector suggests this is by design, but I can't really find a reason why it would have to be so. An example to reproduce it is:


[1..10] |> List.length
[10..1] |> List.length

The first will print out 10 while the second will print out 0. Tests were made in F# CTP 1.9.6.2.

EDIT: thanks for suggesting expliciting the range, but there's still one case (which is what inspired me to ask this question) that won't be covered. What if A and B are variables and none is constantly greater than the other although they're always different? Considering that the range expression does not seem to get optimized at compiled time anyway, is there any good reason for the code which determines the step (not explicitly specified) in case A and B are ints not to allow negative steps?

Upvotes: 8

Views: 3683

Answers (5)

Adam Wright
Adam Wright

Reputation: 49376

What "should" happen is, of course, subjective. Normal range notation in my mind defines [x..y] as the set of all elements greater than or equal to x AND less than or equal to y; an empty set if y < x. In this case, we need to appeal to the F# spec.

Range expressions expr1 .. expr2 are evaluated as a call to the overloaded operator (..), whose default binding is defined in Microsoft.FSharp.Core.Operators. This generates an IEnumerable<_> for the range of values between the given start (expr1) and finish (expr2) values, using an increment of 1. The operator requires the existence of a static member (..) (long name GetRange) on the static type of expr1 with an appropriate signature.

Range expressions expr1 .. expr2 .. expr3 are evaluated as a call to the overloaded operator (.. ..), whose default binding is defined in Microsoft.FSharp.Core.Operators. This generates an IEnumerable<_> for the range of values between the given start (expr1) and finish (expr3) values, using an increment of expr2. The operator requires the existence of a static member (..) (long name GetRange) on the static type of expr1 with an appropriate signature.

The standard doesn't seem to define the .. operator (a least, that I can find). But you should be able to change the binding for types you're interested in to behave in any way you like (including counting down if x > y).

Upvotes: 3

James Hugard
James Hugard

Reputation: 3236

Adam Wright - But you should be able to change the binding for types you're interested in to behave in any way you like (including counting down if x > y).

Taking Adam's suggestion into code:

let (..) a b =
    if a < b then seq { a .. b }
             else seq { a .. -1 .. b }

printfn "%A" (seq { 1 .. 10 })
printfn "%A" (seq { 10 .. 1 })

This works for int ranges. Have a look at the source code for (..): you may be able to use that to work over other types of ranges, but not sure how you would get the right value of -1 for your specific type.

Upvotes: 5

Jonas
Jonas

Reputation: 19642

In haskell, you can write [10, 9 .. 1]. Perhaps it works the same in F# (I haven't tried it)?

edit:

It seems that the F# syntax is different, maybe something like [10..-1..1]

Upvotes: 2

Brian
Brian

Reputation: 118865

As suggested by other answers, you can do

[10 .. -1 .. 1] |> List.iter (printfn "%A")

e.g.

[start .. step .. stop]

Upvotes: 14

Andrew Hare
Andrew Hare

Reputation: 351516

Ranges are generally expressed (in the languages and frameworks that support them) like this:

low_value <to> high_value

Can you give a good argument why a range ought to be able to be expressed differently? Since you were requesting a range from a higher number to a lower number does it not stand to reason that the resulting range would have no members?

Upvotes: 1

Related Questions