F# operator overloading for conversion of multiple different units of measure

爱⌒轻易说出口 提交于 2019-11-30 17:51:06

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>

But it's really hacky, I wouldn't recommend it.

UPDATE

Based on the comments here are some answers:

  • This technique uses overloads which are resolved at compile-time, so there is no performance penalty at run-time. It is based on what I wrote some time ago in my blog.

  • 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.

  • 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'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.

Conclusion

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.

This isn't possible directly within F#. There isn't a way to have the "auto-conversion" succeed directly without specifying the conversion for the types. You would have to explicitly call your conversion functions (seconds_per_minute, etc).

However, Phil Trelford demonstrated a mechanism by which you could create runtime classes which do support this, though with slightly different syntax. Using his types, you could write:

let duration = 1.0 * SI.hours + 2.0 * SI.minutes + 3.0 * SI.seconds
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!