F# operator overloading for conversion of multiple different units of measure
f#, f#-3.0, units-of-measurement
Solution
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.
UPDATE 2
I just found a better solution:
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
which is a bit more scalable, so I would consider using it in production code.
Problem
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 ``` 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. Is this possible to do in F#?