fsharp / fsharp/fslang-suggestions
Ease conversion between Units of Measure (UoM) and undecorated numerals and simplify casting
- Dominant language
- No language data
- Stars
- 373
- Forks
- 21
- PR merge metrics
- No merged PRs in 30d
Description
# Ease of use for Units of Measure (UoM)
After being asked 3-4x in just as many weeks on Slack by different newcomers and experienced programmers alike, I propose we introduce a set of intrinsics related to UoM to make it less painful to convert to undecorated numerals and back, and to simplify conversion between numeric types.
I suggest to do that by adding a few sensibly-named functions to the `LanguagePrimitives`.
## Remove UoM
Pair each `LanguagePrimitives.XXXWithMeasure` with a `LanguagePrimitives.XXXWithoutMeasure`.
## Remove UoM generically
Add `LanguagePrimitives.RemoveMeasure: 'T -> 'T`.
## Add UoM generically
It's already possible to add UoM for specific types, but not generically for any type. We could add `LanguagePrimitives.WithMeasure: 'T -> 'T`, where `'T` is one of the signed integral types.
## Support conversion
If at all possible, I suggest to make all cast operators sensitive to the target, to make this seamless. That means, allowing:
```
let x: float = float 45
```
See also SO question: https://stackoverflow.com/questions/1894250/f-unit-of-measure-casting-without-losing-the-measure-type
## Allow no-op cast on collections for UoM
It's currently impossible to remove `Array>` from its `cm` UoM, unless you loop over it. Same is true for any other collection.
EDIT: the `retype` trick below can actually be adopted for this, thanks to @BillHally ([comment](https://github.com/fsharp/fslang-suggestions/issues/892#issuecomment-711059613)).
```f#
let myArray = [|3; 42|]
let x: int array = myArray |> Array.map LanguagePrimitives.WithoutMeasure // this should be optimized away
```
## Existing way of doing this
The existing way of approaching this problem in F# by multiplying by `1<_>` or `1.0<_>`, but this is type-specific. For generics, you can only do so by using unsafe compiler-specific code, like this (courtesy @gusty):
```f#
// use compiler specific code
let inline retype (x: 'T) : 'U = (# "" x: 'U #)
// remove UoM decoration
let inline removeUoM (x: '``T<'M>``) =
let _ = x * (LanguagePrimitives.GenericOne : 'T)
retype x :'T
```
Conversion from `float` to `int` etc is currently possible by reinterpreting the cast, but [hard to discover](https://stackoverflow.com/questions/1894250/f-unit-of-measure-casting-without-losing-the-measure-type) even for experienced users:
```f#
x |> float |> LanguagePrimitives.FloatWithMeasure
```
## Pros and Cons
The advantages of making this adjustment to F# are numerous, just to name a few:
* Better adoption of F# and simpler use, and therefor more use, of UoM
* Solving a hard, and often-asked real-life problem, that really should be trivial to answer
* Prevent users to ubiquitously use box/unbox tricks to reach the same
* Allow coding above scenarios without resorting to deprecated code
* Improve overall code generation by erasing the types when requested
* Prevent looping over entire collections to just remove UoM
The disadvantages of making this adjustment to F# are:
* It's a surface area change
* Leaving this stuff inside `LanguagePrimitives` may still be hard to discover, but at least it gives a "single, good way" of doing things
## Extra information
Estimated cost (XS, S, M, L, XL, XXL): S
I think this isn't very hard to do, except for some optimizations perhaps. The compiler mostly already has ways of coding this internally. Ultimately, we should try to keep the balance of type-safety with UoM, but really shouldn't make it too hard to work with them.
Related library is FSharp.UMX: https://github.com/fsprojects/FSharp.UMX
### Related language suggestions:
* https://github.com/fsharp/fslang-suggestions/issues/911 (suggestion refused on the grounds the up-scale conversion between UoM's is explicitly not supported)
* https://github.com/fsharp/fslang-suggestions/issues/901 (approved, implemented)
* [RFC 'FS-1091-Extend-Units-of-Measure'](https://github.com/fsharp/fslang-design/blob/main/FSharp-6.0/FS-1091-Extend-Units-of-Measure.md)
* [Implementation PR](https://github.com/dotnet/fsharp/pull/9978) **thanks @pblasucci!**
* [Further discussion, adding to .NET 6](https://github.com/dotnet/fsharp/issues/11189)
* https://github.com/fsharp/fslang-suggestions/issues/452 (suggestion not approved as UoM cannot be adopted to any type without it being a breaking change)
### Related suggestions (and SO questions):
Just a bunch of the many roaming questions related to UoM, illustrating the trouble people perceive with it:
* SO: [casting without loosing UoM ](https://stackoverflow.com/questions/1894250/f-unit-of-measure-casting-without-losing-the-measure-type)
* SO: [strip UoM](https://stackoverflow.com/questions/11404027/strip-unit-of-measure-from-array)
* SO: [strip UoM generically](https://stackoverflow.com/questions/14168536/f-generic-units-over-generic-types/14170239#14170239)
* SO: [add UoM in lists or arrays](https://stackoverflow.com/questions/419521/f-units-of-measure-lifting-values-to-floatsomething?rq=1)
* SO: [strip UoM in lists or arrays](https://stackoverflow.com/questions/11404027/strip-unit-of-measure-from-array?noredirect=1&lq=1)
* SO: [convert between types having the same UoM](https://stackoverflow.com/questions/1497035/reusing-units-of-measure-across-different-types?noredirect=1&lq=1)
* SO: [using external non-UoM-aware functions and get back UoM type you put in](https://stackoverflow.com/questions/28732133/f-types-with-units-of-measure-vs-system-math)
* Most recent slack discussion started here: https://fsharp.slack.com/archives/C1R50TKEU/p1593558127321300, scroll down to @gusty's suggestion (my solution didn't work in his case).
## Affidavit (please submit!)
Please tick this by placing a cross in the box:
* [x] This is not a question (e.g. like one you might ask on [stackoverflow](http://stackoverflow.com)) and I have searched stackoverflow for discussions of this issue
* [x] I have [searched both open and closed suggestions on this site](http://github.com/fsharp/fslang-suggestions/issues) and believe this is not a duplicate
* [x] This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it.
Please tick all that apply:
* [x] This is not a breaking change to the F# language design
* [x] I or my company would be willing to help implement and/or test this: I've looked at the code a bit and would like to tackle this, if an RFC were to be accepted :).
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.