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

后端 未结 2 1734
逝去的感伤
逝去的感伤 2021-01-01 22:56

I want to be able to do this:

let duration = 1 + 2 + 3

with the following types and functions (a

相关标签:
2条回答
  • 2021-01-01 23:36

    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
    
    0 讨论(0)
  • 2021-01-01 23:48

    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.

    0 讨论(0)
提交回复
热议问题