Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can existing types be extended to work with Seq.sum, etc?

Tags:

f#

Been working with a lot of TimeSpans recently, and have a need to get sums & averages.
However, TimeSpan defines neither operator get_Zero nor DivideByInt, so Seq.sum and Seq.average can't be used directly with this type. The following fails to compile:

open System
type System.TimeSpan
    with
        static member Zero with get() = TimeSpan()
        static member (/) (n:DateTime, d:int) = DateTime( n.Ticks / (int64) d )

let ts = [ TimeSpan(10L); TimeSpan(99L) ]
let sum = ts |> Seq.sum
let avg = ts |> Seq.average
  • Error: The type 'TimeSpan' does not support any operators named 'get_Zero'
  • Error: The type 'TimeSpan' does not support any operators named 'DivideByInt'
  • Warning: Extension members cannot provide operator overloads. Consider defining the operator as part of the type definition instead.

Is there some F# magic that can define these operators on an existing type?

I know the following will work (and should be more efficient to boot), but I'm still curious about the above so I can add it to my toolbox for use with other types.

let sum = TimeSpan( ts |> Seq.sumBy (fun t -> t.Ticks) )
let avg = TimeSpan( let len = ts |> Seq.length in sum.Ticks / int64 len )
like image 933
James Hugard Avatar asked Jul 11 '10 15:07

James Hugard


2 Answers

As far as I know, static member constraints (that are used by functions like Seq.sum) are not able to discover members that are added by type extensions (essentially, extension methods), so I don't think there is a direct way of doing this.

The best option I can think of is to creat a simple wrapper around the System.TimeSpan struct. Then you can define all the required members. The code would look like this:

[<Struct>]
type TimeSpan(ts:System.TimeSpan) =
  member x.TimeSpan = ts
  new(ticks:int64) = TimeSpan(System.TimeSpan(ticks))
  static member Zero = TimeSpan(System.TimeSpan.Zero)
  static member (+) (a:TimeSpan, b:TimeSpan) = 
    TimeSpan(a.TimeSpan + b.TimeSpan)
  static member DivideByInt (n:TimeSpan, d:int) = 
    TimeSpan(n.TimeSpan.Ticks / (int64 d)) 

let ts = [ TimeSpan(10L); TimeSpan(99L) ] 
let sum = ts |> Seq.sum 
let avg = ts |> Seq.average 

I called the type TimeSpan, so it hides the standard System.TimeSpan type. However, you still need to write ts.TimeSpan when you need to access the underlying system type, so this isn't as nice as it could be.

like image 111
Tomas Petricek Avatar answered Oct 19 '22 20:10

Tomas Petricek


Mhh the following is rather ugly, but it works. Does it help? I define a wrapper for TimeSpan that can implicitly be converted back to a TimeSpan.

type MyTimeSpan(ts : TimeSpan) =
    member t.op_Implicit : TimeSpan = ts
    static member (+) (t1 : MyTimeSpan, t2 : MyTimeSpan) =
        new MyTimeSpan(TimeSpan.FromTicks(t1.op_Implicit.Ticks + t2.op_Implicit.Ticks))
    static member Zero = new MyTimeSpan(TimeSpan.Zero)
    static member DivideByInt (t : MyTimeSpan, i : int) =
        new MyTimeSpan(TimeSpan.FromTicks(int64 (float t.op_Implicit.Ticks / float i)))

let toMyTS ts = new MyTimeSpan(ts)

let l = [TimeSpan.FromSeconds(3.); TimeSpan.FromSeconds(4.)]
            |> List.map toMyTS
            |> List.average
like image 3
Mau Avatar answered Oct 19 '22 21:10

Mau