Nice. The magic has to happen in Unit. Citing page 18 of the linked document:
For languages such as Pascal and Java that support only type checking, it is
relatively easy to add support for checking units-of-measure, using the procedure
outlined in the previous section. But for F#, we go further, and support full type
inference. To do this, we must not only check equations; we must solve them.
…which means that when, for example, multiplying two numbers with the unit meter (m) you would get square meter (m*m, or m^2).
But I am afraid this can only be caught during runtime, not compile time.
You can do it in compile time with complicated generics like Value<T : Unit>. But I think it does not worth the time spent since in any case you will work with some user input that could not be checked in compile time.
It is very hard to do this compile safe with generics in the case of derived units such as m/s or m/s^2. Even in C++ with full templates it is very hard. The best solution would be to do it with manual overloads/extension functions and not allow division/multiplication of multi-unit values otherwise.
The struggle in jvm based languages with a units system is balancing the desire to have compile units checking (I can’t assign a length to a variable meant to hold time) vs. wanting to keep it lightweight (not having every value be a full blown object on the heap). There is no way to have both in Java 9 and this can only be solved with value objects which are part of Project Valhalla.
One thing to be careful of when defining a type-safe units system is that you want to focus on what the quantity is meant to represent not the units used for the quantity. So Length and Time are different quantities and thus different types. Centimeters and Inches are different ways to create and extract a Length quantity and are not type incompatible. This is analagous to the way a date in Java is an instant in time. There are not different date types for each time zone.
I think it’s more than just that, even if you’re willing to create objects for quantities that associate a number with a unit the generics get really complicated really fast with derived units of derived units of derived units and so on. There is a JSR to add units to the JVM, it creates objects and cannot infer units at compile time. I think the only reason is the impracticality of very complex generic types, I think this can be remedied with type aliases. Basically you only define base units and dimensions for example: Length and Time. Then speed is DerrivedDimension<Length, Time>, similar to the example in the second link @fvasco provided. And the only way you explicitly define speed is with a typealias Speed = DerrivedDimension<Length, Time>. However I do not think it is possible to do this in a way that is compatible with JSR-363 (not sure how important that is).
The plus side is I do think it is possible to make a clean implementation of a statically checked inferred units library for Kotlin using typealias and in a way that should work across all targets. Of course until value types are added there will be extraneous object creation. I made Kotlin extensions for uom-se and I’m currently using that as a units library, this was not very much work because most of the work is done in the underlying Java library. I would eventually like to make a Kotlin specific units library that is statically checked for inferred units and leverages typealias but I think this would be a ton of work to do well (if it is indeed possible to implement well at all) so hopefully someone else does this
Units of measure at runtime
An issue that you may run into is that units of measure are not part of the .NET type system.
F# does stores extra metadata about them in the assembly, but this metadata is only understood by F#.
This means that there is no (easy) way at runtime to determine what unit of measure a value has, nor any way to dynamically assign a unit of measure at runtime.
It also means that there is no way to expose units of measure as part of a public API to another .NET language (except other F# assemblies).
So, I thought that maybe the information about units of measure should not be entirely based on generics, instead utilizing annotations, or maybe it is possible to combine generics and annotations in some sensible way? Could annotation processors help making this work during compile time? I have never used them myself.
@Unit(meter / second^2)
val acceleration = 2.0
val time = 10.0
val speed = acceleration * time
speed.unit // meter / second
val pressure = 10.0
speed + pressure // Runtime unit error, ideally we'd like to check this at compile time (annotation processors?)
What you are talking about is the pre-processor and a complex one.You can type check variables declarations (you can do it with generics as well), but what about expressions? You will either have to annotate any expression results by hand or perform full language analysis.
What I don’t understand is why do you need it? What advantages does static check have? If you want to check types without calculating expression then just make calculations lazy. It requires pretty little, just two-component object containing units and lambda-expression.
I am merely pointing to other places where this has been discussed. I have no idea if these stack up to manifold, but I will point to this documentation for the first library (http://units.kunalsheth.info/) which shows some unit math like 100.Mile / Hour