可以扩展现有类型以使用Seq.sum等吗?

时间:2010-07-11 15:57:16

标签: f#

最近一直在与很多TimeSpans合作,并且需要获得总和和平均值。
但是,TimeSpan既不定义运算符get_Zero也不定义DivideByInt,因此Seq.sum和Seq.average不能直接用于此类型。以下无法编译:

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
  • 错误:“TimeSpan”类型不支持任何名为“get_Zero”的运算符
  • 错误:“TimeSpan”类型不支持任何名为“DivideByInt”的运算符
  • 警告:扩展成员无法提供操作员重载。考虑将运算符定义为类型定义的一部分。

是否有一些F#magic可以在现有类型上定义这些运算符?

我知道以下内容可行(并且应该更有效地启动),但我仍然对上述内容感到好奇,所以我可以将它添加到我的工具箱中以便与其他类型一起使用。

let sum = TimeSpan( ts |> Seq.sumBy (fun t -> t.Ticks) )
let avg = TimeSpan( let len = ts |> Seq.length in sum.Ticks / int64 len )

2 个答案:

答案 0 :(得分:9)

据我所知,静态成员约束(由Seq.sum等函数使用)无法发现由类型扩展(实质上是扩展方法)添加的成员,所以我不认为有一种直接的方法可以做到这一点。

我能想到的最佳选择是在System.TimeSpan结构周围创建一个简单的包装器。然后,您可以定义所有必需的成员。代码如下所示:

[<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 

我调用了类型TimeSpan,因此它隐藏了标准的System.TimeSpan类型。但是,当您需要访问基础系统类型时,仍然需要编写ts.TimeSpan,因此这不是很好。

答案 1 :(得分:3)

嗯,以下是相当丑陋,但它的确有效。有帮助吗?我为TimeSpan定义了一个包装器,可以隐式转换回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