F#运算符重载,用于转换多个不同的度量单位 [英] F# operator overloading for conversion of multiple different units of measure
问题描述
我希望能够做到这一点:
I want to be able to do this:
let duration = 1<hours> + 2<minutes> + 3<seconds>
具有以下类型和功能(可能还有更多的计量单位):
with the following types and functions (and possibly more units of measure):
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"应该用来添加分钟和秒.
So basically "hours_to_minutes" should be used for adding hours and minutes, and "minutes_to_seconds" should be used for adding minutes and seconds when I type it in like above.
这可以在F#中执行吗?
推荐答案
实际上有可能,有一种方法可以做到这一点:
Actually it is possible, there is a way to do this:
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,我不推荐.
But it's really hacky, I wouldn't recommend it.
更新
根据此处的评论提供一些答案:
Based on the comments here are some answers:
-
此技术使用在编译时解决的重载,因此在运行时不会降低性能.它基于我前一段时间在我的博客中写的内容.
要添加更多的重载,您将不得不添加更多的虚拟参数(D3
,D4
,...),最终,如果您决定添加一些与现有重载冲突的重载,则可能必须使用三元运算符(?<-)
或具有显式静态成员约束的函数调用. 此处是示例代码.
To add more overloads you will have to add more dummy parameters (D3
, D4
, ...) and eventually if you decide to add some overloads which conflicts with an existing one you may have to use a ternary operator (?<-)
or a function call with explicit static member constraints. Here's a sample code.
我想我不会使用它,因为它需要许多技巧(虚拟重载和2个虚拟类型)并且代码变得可读性较差.最终,如果F#为基于重载的内联函数添加更多支持,我肯定会考虑的.
I think I wouldn't use it since it requires many hacks (a Dummy overload and 2 dummy types) and the code becomes less readable. Eventually if F# adds more support for inline functions based on overloads I would definitely consider it.
Phil Trelford的技术(在里德的答案中提到)有效在运行时,第三个选择是使用幻像类型,它可能需要更少的hack.
Phil Trelford's technique (mentioned in Reed's answer) works at run-time, a 3rd option would be to use phantom types, it may require less hacks.
结论
如果必须在所有替代方法之间进行选择,我将使用此技术,但要在调用站点更加明确,我的意思是我将定义诸如minutes
,seconds
的转换函数,并且在调用站点上我会这样写:
If I had to choose between all alternatives I would use this technique but being more explicit at the call site, I mean I would define conversion functions like minutes
, seconds
and that way at the call site I would write:
let duration = seconds 1<hours> + seconds 2<minutes> + 3<seconds>
然后要定义这些转换函数,我将使用重载,但与重新定义现有的二进制运算符相比,它不那么笨拙.
And then to define those conversion functions I would use overloads, but it would be less hacky than re-defining an existing binary operator.
这篇关于F#运算符重载,用于转换多个不同的度量单位的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!