Measured: intuitive, type-safe units

Measured provides a safe and simple way to work with units of measure. It uses the compiler to ensure correctness, and provides intuitive, mathematical operations to work with any units. This means you can write more robust code that avoids implicit units. Time handling for example, is often done with implicit assumptions about milliseconds vs microseconds or seconds. Measured helps you avoid pitfalls like these.
interface Clock {
fun now(): Measure<Time>
}
{
reportTimeInMillis(duration `` milliseconds)
}
{
startTime = clock.now()
handleUpdate(clock.now() - startTime)
}
{}
Complex Units
Use division and multiplication to create compound measures. Convert between these safely and easily with the as and in methods.
val velocity = 5 * meters / seconds
val acceleration = 9 * meters / (seconds * seconds)
val time = 1 * minutes
val distance = velocity * time + 1.0 / 2 * acceleration * time * time
println(distance )
println(distance `` kilometers)
println(distance `` miles )
println( * miles / hours `` meters / seconds)
The as method converts a Measure from its current Unit to another. The result is another Measure. While in returns the magnitude of a Measure in the given Unit.
Avoid Raw Values
Measure's support of math operators helps you avoid working with raw values directly.
val marathon = 26 * miles
val velocity = 3 * kilometers / hours
val timeToRunHalfMarathon = (marathon / 2) / velocity
fun calculateTime(distance: Measure<Length>, velocity: Measure<>): Measure<Time> {
distance / velocity
}
Extensible
You can easily add new conversions to existing units and they will work as expected.
val hands = Length("hands", 0.1016)
val l1 = 5 * hands
val l2 = l1 `as` meters
v: Measure<Velocity> = * hands / hours
println()
println(v `` hands / seconds)
println(v `` miles / hours )
You can also define entirely new units with a set of conversions and have them interact with other units.
Current Limitations
Measured uses Kotlin's type system to enable compile-time validation. This works really well in most cases, but there
are things the type system currently does not support. For example, Units and Measures are order-sensitive.
val a: UnitsProduct<Angle, Time> = radians * seconds
val b: UnitsProduct<Time, Angle> = seconds * radians
Notice the types for a and b are different.
This can be mitigated on a case by case basis with explicit extension functions that help with order. For example,
you can ensure that kg is sorted before m by providing the following extension.
operator fun Length.times(mass: Mass) = mass * this
val f1 = 1 * (kilograms * meters) / (seconds * seconds)
val f2 = 1 * (meters * kilograms) / (seconds * seconds)
You can also define an extension on Measure to avoid needing parentheses around kilograms and meters.
operator fun Measure<Length>.times(mass: Mass) = amount * (units * mass)
Measured currently only supports linear units where all members of a given unit are related by a single magnitude. This
applies to many units, but Fahrenheit and Celsius are examples of temperature units that requires more than a multiplier
for conversion.
Installation
Measured is a Kotlin Multi-platform library that targets a wide range of platforms. Simply add a dependency to your app's Gradle build file as follows to start using it.
repositories {
mavenCentral()
}
dependencies {
implementation("io.nacular.measured:measured:$VERSION")
}
Contact
- Please see issues to share bugs you find, make feature requests, or just get help with your questions.
- Let us know what you think by leaving a comment or a star ⭐️.