我希望能够做到这一点:
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# 中可以做到吗?
其实是可以的,有办法做到:
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
更具可扩展性,所以我会考虑在生产代码中使用它。
这在 F# 中是不可能的。如果不指定类型的转换,则无法直接让“自动转换”成功。您必须显式调用转换函数(
seconds_per_minute
等)。
但是,Phil Trelford 演示了一种机制,通过该机制,您可以 创建支持此功能的运行时类,尽管语法略有不同。使用他的类型,你可以写:
let duration = 1.0 * SI.hours + 2.0 * SI.minutes + 3.0 * SI.seconds