Compile-time units resolution
Use compile-time units resolution in order to avoid number boxing.
The basic idea is the following:
- Make
Measurementan interface. And keep the implementation you already have for dynamic units. - Add an inline
Measurementimplementation like this:
inline class InlineMeasurement(<U: Units>(val value: Double): Measurement<U>
- Add a resolver:
inline fun <reified U:Units> resolveUnits(): Unitsand use it to loadUnitsobject when it is needed. The resolver could work in different ways. The simplest way is to loadobjectInstancefrom reflects.
Both implementations could be used in different cases: boxing one in cases, when the Units are dynamic, inline when you can infer everything in compile-time.
This might be achievable by making Measure itself inline. Doing so would remove its ability to track the current unit, so its value would be in the "base" unit. This means:
val a = 60 * minutes
// a == Measure(60 * 60 * 1000), since minutes.ratio == 60000
All operations would be kept in the base unit:
val speed = 60 * miles / hours
// speed would be 0.0268224 m/ms (60 * 1609.344 / 3600000)
// Measured default Time unit is milliseconds
Everything else should work as it does now, with the exception of Measure.as(Unit) and Measure.toString(). The former wouldn't make sense anymore since Measure would never be in anything but base units. The toString behavior would need a new method; something like:
inline class Measure<T: Units>(val value: Double) {
//...
fun display(with: Units): String {...}
//...
}
Unfortunately, inline classes are boxed whenever they are consumed as an interface they implement. So adding one would nullify the value of this change.
However, a major downside of exposing an inline class in the API is it requires consumers to have inline classes as part of their function signatures. Which prevents Java from calling them due to name mangling.