I recently released Measured . A library that makes units a lot simpler and intuitive to work with. It uses the compiler to enforce correctness and lets you combine units into more complex ones using math operators. It is also extensible, making it easy to define your own units.
val velocity = 5 * meters / seconds
val acceleration = 9 * meters / (seconds * seconds)
val time = 1 * minutes
// d = vt + ½at²
val distance = velocity * time + 1.0/2 * acceleration * time * time
println(distance ) // 16500 m
println(distance `as` kilometers) // 16.5 km
println(distance `as` miles ) // 10.25262467191601 mi
println(5 * miles / hours `as` meters / seconds) // 2.2352 m/s
I tried to do something similar in Java for a project a while back for distances and velocity but it was … verbose to say the least. This is really nice, wish I had a project I needed it for!
I’ve also found it valuable when working with simple units like time (see Doodle). It is much safer than relying on conventions: like all times are in milliseconds.
Plus: you can enforce some constraints directly when the object is created. If you had for example a Length object you would likely exclude negative length. In my current project we are using little classes for almost everything and it really pays off. It is not only clear what exactly this String, Int or whatever means, but the type is also enforcing and documenting the rules.
Example:
data class VehicleCode(val value: String) {
init {
require(value.length == 20) { "Value must have 20 characters." }
require(value[7] == '-') { "Value must have '-' at position 8." }
}
}
Since creating such classes is so easy in Kotlin, I think this should become a common best-practice in Kotlin. And libraries like “Measured” make it even easier!
You should read into Kotlin Arrow Meta’s refinement types. It adds a specific syntax for these with nice behavior (as in, the compiler can automatically cast from an Int to a VehicleCode if the requirements are satisfied, etc)
I would recommend to be consistent with the Kotlin standard library for type conversion!
The stdlib uses extension functions like Duration.toLongMilliseconds(): Long or Int.toLong(). It is more tedious for the library developer to cover all cases, but is far more intuitive to use as library consumer since the IDE can easily hint what you need!
I’d love to find a better pair of conversion method names. How would you use in? It maps to fun contains(...): Boolean. So I don’t see how it can work for conversion.
Most code will use in as the terminal point for a Measure. Or when passing one to code that uses raw numbers. The use of as should be less common, and likely pertain to displaying values like in the doc examples.
There’s already a perfectly good standard for conversion functions: .toXxx(). Everything else uses it; it’s universally understood, very clear, and reads well.
So why does this project do something different, something that (judging from this thread) causes some confusion, as well as awkward syntax (backticks)? Why is this any different from converting a Long to an Int, a List to a Set, or a DateTime to an Instant?
IMHO, infix functions and operator overloads are best used sparingly, only where there’s a clear reason not to do things the ordinary way, where the intent is crystal clear and matches any existing usage. This doesn’t seem to be one of those situations.
as seems a particularly bad choice, as it usually means something different: .asXxx() functions create a view of an existing object, one which reflects changes to that object (and whose changes are applied back to it). Your use looks like that, but behaves differently, which could lead to subtle bugs.
Unfortunately the toXxx() convention doesn’t work well for this problem. Units are not a small, closed set that translate well to a set of functions. That means you’d have an explosion of very specialized functions to cover the wide range of units. Not to mention complex units like Newtons. Moreover, Units like Newton can be represented by a wide range of combinations. So the typealias for it would be something like:
And conversions would need to support any way of representing a Newton, say: kilograms * miles / (hours * minutes). This is a big reason why the framework does not provide specialized conversion methods.
Also, notice that the as and in methods take instances of Units and not the class itself. Saying value 'as' Newton does not make sense because you can’t convert between Unit types, only between unit instances. So Newton here is a type, but there are many ways to represent it that do not require new classes.
Bespoke conversions are not defined in the framework because of these differences. You can always add them for specific Measure<T>s as extension methods if you have a set that are particularly important for your use-case. But I’m not sure how much easier, or readable that is vs the current approach.