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

use*_*923 12 f# units-of-measurement f#-3.0

我希望能够这样做:

let duration = 1<hours> + 2<minutes> + 3<seconds>
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

因此,基本上应该使用"hours_to_minutes"来添加小时和分钟,并且当我在上面键入时,应该使用"minutes_to_seconds"来添加分钟和秒.

这可能在F#中做到吗?

Gus*_*Gus 15

实际上有可能,有一种方法可以做到这一点:

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>
Run Code Online (Sandbox Code Playgroud)

但它真的很hacky,我不推荐它.

UPDATE

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

  • 此技术使用在编译时解析的重载,因此在运行时不会降低性能.它基于我前一段时间在博客中写的内容.

  • 要添加更多重载,您必须添加更多伪参数(D3,D4...),最终如果您决定添加一些与现有重载冲突的重载,您可能必须使用三元运算符(?<-)或带有显式静态成员的函数调用限制.这是一个示例代码.

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

  • Phil Trelford的技术(在Reed的答案中提到)在运行时工作,第三种选择是使用幻像类型,它可能需要更少的黑客攻击.

结论

如果我把所有的替代品之间选择,我会用这种方法,但是是在调用点,我的意思是我会这样定义转换功能更加明确minutes,seconds而且这种方式在调用点我会写:

let duration = seconds 1<hours> + seconds 2<minutes> + 3<seconds>
Run Code Online (Sandbox Code Playgroud)

然后定义那些我将使用重载的转换函数,但它不如重新定义现有的二元运算符那么hacky.

  • 好吧,我不会使用它,因为代码很难读.但重要的是要注意,没有运行时性能损失,因为重载在编译时得到解决.有关此技术的更多信息,请访问http://nut-cracker.com.ar/ (2认同)

Ree*_*sey 5

这在F#中是不可能的.如果不指定类型的转换,则无法直接"自动转换"成功.您必须明确调用转换函数(seconds_per_minute等).

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

let duration = 1.0 * SI.hours + 2.0 * SI.minutes + 3.0 * SI.seconds
Run Code Online (Sandbox Code Playgroud)