Can existing types be extended to work with Seq.sum, etc?
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. T开发者_如何学JAVAhe 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 )
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.
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
精彩评论