2014-02-18 44 views
12

的多个不同单位的转换我希望能够做到这一点:F#的运算符重载的措施

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#中做的吗?

回答

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> 

但它确实哈克,我不会推荐它。

UPDATE

基于这里的评论是一些答案:

  • 该技术使用在编译时解析重载,所以在运行时没有性能损失。这是基于我前段时间在my blog中写的。

  • 要添加更多的重载,你将不得不增加更多的虚拟参数(D3D4,...),并最终如果你决定要添加一些重载与现有的冲突,那么你可能需要使用一个三元操作(?<-)或具有显式静态成员约束的函数调用。 Here's a sample code

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

  • Phil Trelford's technique(在里德的回答中提到)在运行时工作,第三种选择是使用幻像类型,它可能需要更少的黑客。

结论

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

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

然后定义这些转换功能,我会用重载,但它是不是重新定义EXIS少哈克ting二元运算符。

+0

哇,这太棒了!我有几个问题,但: 1.为什么你不推荐它? 2.我的类型安全性是否松动? 3.如果您不得不选择,那么您宁愿使用Phil Trelfort的运行时解决方案吗? 4.美元实际上做了什么? 谢谢! – user3323923

+0

顺便说一句,我希望我能接受两个答案。 – user3323923

+0

@ user3323923,Gustavo使用括号'($)'定义了一个运算符'$'。 @Gustavo,我回答这个问题,你为什么不使用它?这似乎是一个合法的解决方案。 –

5

这在F#中是不可能的。没有办法让“自动转换”直接成功,而无需指定类型的转换。您必须明确地调用您的转换函数(seconds_per_minute等)。

但是,Phil Trelford演示了一种机制,通过这种机制,您可以使用create runtime classes which do support this,但语法略有不同。用他的类型,你可以写:

let duration = 1.0 * SI.hours + 2.0 * SI.minutes + 3.0 * SI.seconds