F# 运算符重载以转换多个不同的度量单位

问题描述 投票:0回答:2

我希望能够做到这一点:

let duration = 1<hours> + 2<minutes> + 3<seconds>

具有以下类型和功能(可能还有更多计量单位):

type [<Measure>] seconds     
type [<Measure>] minutes
type [<Measure>] hours

let seconds_per_minute = 60<seconds> / 1<minutes>
let minutes_per_hour = 60<minutes> / 1<hours>

let minutes_to_seconds minutes seconds = minutes * seconds_per_minute + seconds
let hours_to_minutes hours minutes = hours * minutes_per_hour + minutes

所以基本上“hours_to_minutes”应该用于添加小时和分钟,而“minutes_to_seconds”应该用于添加分钟和秒,当我像上面那样输入时。

这在 F# 中可以做到吗?

f# units-of-measurement f#-3.0
2个回答
19
投票

其实是可以的,有办法做到:

type [<Measure>] seconds     
type [<Measure>] minutes
type [<Measure>] hours

let seconds_per_minute = 60<seconds> / 1<minutes>
let minutes_per_hour = 60<minutes> / 1<hours>

let minutes_to_seconds minutes seconds = minutes * seconds_per_minute + seconds
let hours_to_minutes hours minutes = hours * minutes_per_hour + minutes

type D1 = D1
type D2 = D2

type Sum = Sum with
  static member inline ($) (Sum, _:^t when ^t: null and ^t: struct) = id
  static member inline ($) (Sum, b)              = fun _  _  a -> a + b
  static member        ($) (Sum, b:int<minutes>) = fun D1 _  a -> hours_to_minutes   a b
  static member        ($) (Sum, b:int<seconds>) = fun D1 D2 a -> minutes_to_seconds a b    

let inline (+) a b :'t = (Sum $ b) D1 D2 a

let duration = 1<hours> + 2<minutes> + 3<seconds>

但这真的很hacky,我不推荐它。

更新

根据这里的评论有一些答案:

  • 此技术使用在编译时解决的重载,因此在运行时没有性能损失。它基于我前段时间在my blog.

    中写的内容
  • 要添加更多重载,您将不得不添加更多虚拟参数(

    D3
    D4
    ,...),最终如果您决定添加一些与现有重载冲突的重载,您可能必须使用三元运算符
    (?<-)
    或具有显式静态成员约束的函数调用。 这是示例代码.

  • 我认为我不会使用它,因为它需要很多 hack(一个 Dummy 重载和 2 个 dummy 类型)并且代码变得不那么可读。最终,如果 F# 添加更多对基于重载的内联函数的支持,我肯定会考虑它。

  • Phil Trelford 的技术(在 Reed 的回答中提到)在运行时有效,第三种选择是使用幻像类型,它可能需要更少的 hack。

结论

如果我必须在所有备选方案之间做出选择,我会使用这种技术,但在调用站点更明确,我的意思是我会定义转换函数,如

minutes
seconds
,这样在调用站点我会写:

let duration = seconds 1<hours> + seconds 2<minutes> + 3<seconds>

然后为了定义那些转换函数,我会使用重载,但这比重新定义现有的二元运算符要简单得多。

更新 2

我刚刚找到了一个更好的解决方案:

type D1 = interface end
type D2 = interface end
type D3 = interface end

type Sum =
    class
        interface D1
        interface D2
        interface D3
    end

type Sum with
  static member inline ($) (_: D1, _: ^t when ^t: null and ^t: struct) = ()
  static member inline ($) (_: D1, b)               = fun a -> a + b
  static member        ($) (_: D2, b: int<minutes>) = fun a -> hours_to_minutes   a b
  static member        ($) (_: D3, b: int<seconds>) = fun a -> minutes_to_seconds a b

let inline (+) a b :'t = (Unchecked.defaultof<Sum> $ b) a

更具可扩展性,所以我会考虑在生产代码中使用它。


5
投票

这在 F# 中是不可能的。如果不指定类型的转换,则无法直接让“自动转换”成功。您必须显式调用转换函数(

seconds_per_minute
等)。

但是,Phil Trelford 演示了一种机制,通过该机制,您可以 创建支持此功能的运行时类,尽管语法略有不同。使用他的类型,你可以写:

let duration = 1.0 * SI.hours + 2.0 * SI.minutes + 3.0 * SI.seconds
© www.soinside.com 2019 - 2024. All rights reserved.