Libra is a dimensional analysis library based on shapeless, spire and singleton-ops. It contains out of the box support for SI units for all numeric types.

To Use

Libra supports JDK 8, scala versions 2.11.8 and 2.12.1.

Add this to your build.sbt:

libraryDependencies += "com.github.to-ithaca" %% "libra" % "0.2.0"
scalaOrganization := "org.typelevel"
scalacOptions += "-Yliteral-types"

This adds Typelevel Scala.

TL;DR

Example usage:

import spire.implicits._
// import spire.implicits._

import libra._, libra.si._
// import libra._
// import libra.si._

(3.m + 2.m).show
// res0: String = 5 m [L]

(3.m * 2.m).show
// res1: String = 6 m^2 [L^2]

(1.0.km.to[Metre] + 2.0.m + 3.0.mm.to[Metre]).show
// res2: String = 1002.003 m [L]

(3.0.s.to[Millisecond] / 3.0.ms).show
// res3: String = 1000.0  []

3.m + 2.kg //this should fail
// <console>:22: error: These quantities can't be added!
// Most likely they have different dimensions.  If not, make sure that there's an implicit AdditiveSemigroup in scope.
// Left: libra.Quantity[Int,libra.Term[libra.si.package.Length,libra.si.package.Metre,libra.Fraction[Int(1),Int(1)]] :: shapeless.HNil]
// Right: libra.Quantity[Int,libra.Term[libra.si.package.Mass,libra.si.package.Kilogram,libra.Fraction[Int(1),Int(1)]] :: shapeless.HNil]
//        3.m + 2.kg //this should fail
//            ^

Why?

When we deal with numeric quantities, we often resort to Int, Double or Float types. These are incommunicative and error prone.

val distance = 3.0 // 3 m
// distance: Double = 3.0

val time = 2.0 // 2 s
// time: Double = 2.0

val speed = distance + time // Oh no!
// speed: Double = 5.0

There’s a mistake in our formula, but we won’t know without a decent set of tests.

Libra provides a Quantity which wraps base numeric types. It supports compile time dimensional analysis.

import spire.implicits._
// import spire.implicits._

import libra._, libra.si._
// import libra._
// import libra.si._

val distance = 3.0.m
// distance: libra.QuantityOf[Double,libra.si.package.Length,libra.si.package.Metre] = Quantity(3.0)

val time = 2.0.s
// time: libra.QuantityOf[Double,libra.si.package.Time,libra.si.package.Second] = Quantity(2.0)

distance + time
// <console>:33: error: These quantities can't be added!
// Most likely they have different dimensions.  If not, make sure that there's an implicit AdditiveSemigroup in scope.
// Left: libra.Quantity[Double,libra.Term[libra.si.package.Length,libra.si.package.Metre,libra.Fraction[Int(1),Int(1)]] :: shapeless.HNil]
// Right: libra.Quantity[Double,libra.Term[libra.si.package.Time,libra.si.package.Second,libra.Fraction[Int(1),Int(1)]] :: shapeless.HNil]
//        distance + time
//                 ^

(distance / time).show // Yay!
// res6: String = 1.5 m s^-1 [L T^-1]

Credits

Libra makes heavy use of Typelevel Scala, shapeless, spire and singleton-ops. It wouldn’t be possible without these projects, their authors and contributors, so if you like Libra, please check them out.

Libra also uses:

Alternatives

If Libra isn’t quite your cup of tea, definitely take a look at Squants. It’s also a great library for dimensional analysis, with a slightly different scope.