Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F# (.. ..) operator usage/overloading

I'm interested in using/overloading the "range step" operator (.. ..), but I can't for the life of me find out how to use it.

In the documentation it says

// Usage:
start .. step .. finish

but trying that in the F# shell gives errors:

> let x = 1 .. 2 .. 7;;

  let x = 1 .. 2 .. 7;;
  ----------^^

stdin(54,11): error FS0010: Unexpected symbol '..' in binding. Expected incomplete structured construct at or before this point or other token.

However, calling it "explicitly" is possible:

> let x = (.. ..) 1 2 7;;

val x : seq<int>

Is it only possible to use this operator for list/seq construction such as [1..2..7] and seq {1..2..7}?

like image 289
uhrm Avatar asked Jan 13 '12 15:01

uhrm


2 Answers

The uses of this operator are covered by section 6.3.12 of the spec (Range Expressions). The built-in (.. ..) operator works on any type with appropriate (+) and Zero members, but you can redefine it to do something else (note that this example is nonsensical):

let (.. ..) x y z = 
    Seq.map (fun (s:string) -> s.[z] + y) x

let result = seq { ["test"; "func"] .. (char 1) .. 2 } // contains 't' 'o'
like image 95
kvb Avatar answered Nov 11 '22 18:11

kvb


Overriding (.. ..) operator

If you redefine (.. ..) operator as in @kvb's answer, it will override that operator of any type. Since you probably want to make (.. ..) operator work for a custom datatype, overriding static (+) and One members is enough. For example, here's a custom numeric type for modular arithmetic taken from @Tomas's blog:

  type IntegerZ5 = 
   | Z5 of int
   member z.ToInt32() =  
     let (Z5 n) = z in n
   override z.ToString() = 
     sprintf "%d (mod 5)" (z.ToInt32())

   static member Create(n) = 
     let z5 = n % 5
     Z5(max ((z5 + 5) % 5) z5)
   static member (+) (Z5 a, Z5 b) = IntegerZ5.Create(a + b)
   static member (-) (Z5 a, Z5 b) = IntegerZ5.Create(a - b)
   static member (*) (Z5 a, Z5 b) = IntegerZ5.Create(a * b)
   static member Zero = Z5 0
   static member One  = Z5 1

  let inline z5 a = IntegerZ5.Create(a)

While constructing a range starting from the lower bound, (+) and One are used to find the next element. The construction finishes when the next element equals to or exceeds the range's upper bound. Now you can employ IntegerZ5 in any range expression:

  let s1 = seq{z5 37..z5 3};; // seq [Z5 2; Z5 3]
  let s2 = seq{z5 10..z5 22..z5 4};; // seq [Z5 0; Z5 2; Z5 4]

Using (.. ..) operator

Another use of range expression is in for loops. I find it helpful in many cases:

let sum = 
    let mutable s = 0L
    for i in 1L..1000L do (* or 1L..1L..1000L with an explicit step *)
        s <- s + i
    s

because it is more flexible than for...to..do which is restricted to int only and implies a range with a step of 1:

let sum = 
    let mutable s = 0L
    for i = 1L to 1000L do (* doesn't work *)
        s <- s + i
    s
like image 2
pad Avatar answered Nov 11 '22 18:11

pad