F# 运算符重载用于转换多个不同的度量单位
Posted
技术标签:
【中文标题】F# 运算符重载用于转换多个不同的度量单位【英文标题】:F# operator overloading for conversion of multiple different units of measure 【发布时间】:2014-03-18 17:05:16 【问题描述】:我希望能够做到这一点:
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# 中实现吗?
【问题讨论】:
【参考方案1】:其实是可以的,有办法做到这一点:
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,我不会推荐它。
更新
根据 cmets,这里有一些答案:
此技术使用在编译时解决的重载,因此在运行时没有性能损失。它基于我前段时间在my blog 中写的内容。
要添加更多重载,您将不得不添加更多虚拟参数(D3
、D4
、...),最终如果您决定添加一些与现有重载冲突的重载,您可能必须使用三元运算符(?<-)
或具有显式静态成员约束的函数调用。 Here's a sample code.
我认为我不会使用它,因为它需要很多技巧(一个虚拟重载和 2 个虚拟类型)并且代码变得不那么可读。最终,如果 F# 为基于重载的内联函数添加更多支持,我肯定会考虑它。
Phil Trelford's technique(在 Reed 的回答中提到)在运行时工作,第三种选择是使用幻像类型,它可能需要更少的 hack。
结论
如果我必须在所有备选方案之间进行选择,我会使用这种技术,但在调用站点更明确,我的意思是我将定义转换函数,如 minutes
、seconds
,并且在调用站点我会这样写:
let duration = seconds 1<hours> + seconds 2<minutes> + 3<seconds>
然后为了定义那些转换函数,我会使用重载,但它会比重新定义现有的二元运算符更简单。
【讨论】:
哇,这太棒了!不过我有几个问题: 1. 你为什么不推荐它? 2. 我松了类型安全吗? 3. 如果必须选择,您更愿意选择 Phil Trelfort 的运行时解决方案还是这个? 4. $实际上是做什么的?谢谢! 好吧,我不会使用它,因为代码很难阅读。但重要的是要注意没有运行时性能损失,因为重载是在编译时解决的。有关此技术的更多信息nut-cracker.com.ar Phil Trelford 的方法是运行时,这是编译时。 SI 模块没有这样做,它只是定义度量,而不是重载。 是的,它将重新路由到这些重载。第一种方法是 hack,它永远不会到达那里,第二种是通用情况,其余的是所需的重载。顺便说一句,问题没有提到他不想调用转换方法。 是的,它是任意的。例如,您可以将其更改为 (=>),它仍然可以工作。添加更多重载是可能的,但会更棘手。【参考方案2】:这在 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
【讨论】:
以上是关于F# 运算符重载用于转换多个不同的度量单位的主要内容,如果未能解决你的问题,请参考以下文章