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#?
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With