UnittedQuantity is the primary case class in axle.quanta

The axle.quanta package models units of measurement. Via typeclasses, it implements expected operators like + - * over by, a unit conversion operator in, and a right associative value constructor *:

The “quanta” are Acceleration, Area, Angle, Distance, Energy, Flow, Force, Frequency, Information, Mass, Money, MoneyFlow, MoneyPerForce, Power, Speed, Temperature, Time, and Volume. Axle’s values are represented in such a way that a value’s “quantum” is present in the type, meaning that nonsensical expressions like mile + gram can be rejected at compile time.

Additionally, various values within the Quantum objects are imported. This package uses the definition of “Quantum” as “something that can be quantified or measured”.

import cats.implicits._
import spire.implicits._
import axle._
import axle.quanta._
import axle.jung._

Quanta each define a Wikipedia link where you can find out more about relative scale:

Distance().wikipediaUrl
// res0: String = http://en.wikipedia.org/wiki/Orders_of_magnitude_(length)

A visualization of each Quantum (like the one for Distance shown above) is produced with:

import edu.uci.ics.jung.graph.DirectedSparseGraph
import cats.Show
import axle.algebra.modules.doubleRationalModule

implicit val distanceConverter = Distance.converterGraphK2[Double, DirectedSparseGraph]

implicit val showDDAt1 = new Show[Double => Double] {
def show(f: Double => Double): String = f(1d).toString
}

import axle.visualize._
import axle.web._

svg(DirectedGraphVisualization(distanceConverter.conversionGraph), "Distance.svg")

## Units

A conversion graph must be created with type parameters specifying the numeric type to be used in unitted quantity, as well as a directed graph type that will store the conversion graph. The conversion graphs should be placed in implicit scope. Within each are defined units of measurement which can be imported.

implicit val massConverter = Mass.converterGraphK2[Double, DirectedSparseGraph]
import massConverter._

implicit val powerConverter = Power.converterGraphK2[Double, DirectedSparseGraph]
import powerConverter._

implicit val energyConverter = Energy.converterGraphK2[Double, DirectedSparseGraph]
import energyConverter._

import axle.algebra.modules.doubleRationalModule

implicit val distanceConverter = Distance.converterGraphK2[Double, DirectedSparseGraph]
import distanceConverter._

implicit val timeConverter = Time.converterGraphK2[Double, DirectedSparseGraph]
import timeConverter._

Standard Units of Measurement are defined:

gram
// res11: axle.quanta.UnitOfMeasurement[axle.quanta.Mass] = UnitOfMeasurement(gram,g,None)

foot
// res12: axle.quanta.UnitOfMeasurement[axle.quanta.Distance] = UnitOfMeasurement(foot,ft,None)

meter
// res13: axle.quanta.UnitOfMeasurement[axle.quanta.Distance] = UnitOfMeasurement(meter,m,None)

## Construction

Values with units are constructed with the right-associative *: method on any spire Number type as long as a spire Field is implicitly available.

10d *: gram
// res14: axle.quanta.UnittedQuantity[axle.quanta.Mass,Double] = UnittedQuantity(10.0,UnitOfMeasurement(gram,g,None))

3d *: lightyear
// res15: axle.quanta.UnittedQuantity[axle.quanta.Distance,Double] = UnittedQuantity(3.0,UnitOfMeasurement(lightyear,ly,Some(http://en.wikipedia.org/wiki/Light-year)))

5d *: horsepower
// res16: axle.quanta.UnittedQuantity[axle.quanta.Power,Double] = UnittedQuantity(5.0,UnitOfMeasurement(horsepower,hp,None))

3.14 *: second
// res17: axle.quanta.UnittedQuantity[axle.quanta.Time,Double] = UnittedQuantity(3.14,UnitOfMeasurement(second,s,Some(http://en.wikipedia.org/wiki/Second)))

200d *: watt
// res18: axle.quanta.UnittedQuantity[axle.quanta.Power,Double] = UnittedQuantity(200.0,UnitOfMeasurement(watt,W,None))

## Conversion

A Quantum defines a directed graph, where the UnitsOfMeasurement are the vertices, and the Conversions define the directed edges. See the Graph package for more on how graphs work.

Quantities can be converted into other units of measurement. This is possible as long as 1) the values are in the same Quantum, and 2) there is a path in the Quantum between the two.

10d *: gram in kilogram
// res19: axle.quanta.UnittedQuantity[axle.quanta.Mass,Double] = UnittedQuantity(0.01,UnitOfMeasurement(kilogram,Kg,None))

Converting between quanta is not allowed, and is caught at compile time:

(1 *: gram) in mile
// <console>:60: error: type mismatch;
//  found   : axle.quanta.UnitOfMeasurement[axle.quanta.Distance]
//  required: axle.quanta.UnitOfMeasurement[axle.quanta.Mass]
//        (1 *: gram) in mile
//                       ^

## Show

A witness for the cats.Show typeclass is defined, meaning that string or show will return a String representation, and print will send it to stdout.

string(10d *: gram in kilogram)
// res21: String = 0.01 Kg

## Math

Addition and subtraction are defined on Quantity by converting the right Quantity to the unit of the left.

(1d *: kilogram) + (10d *: gram)
// res22: axle.quanta.UnittedQuantity[axle.quanta.Mass,Double] = UnittedQuantity(1010.0,UnitOfMeasurement(gram,g,None))

(7d *: mile) - (123d *: foot)
// res23: axle.quanta.UnittedQuantity[axle.quanta.Distance,Double] = UnittedQuantity(36837.0,UnitOfMeasurement(foot,ft,None))

Addition and subtraction between different quanta is rejected at compile time:

(1d *: gram) + (2d *: foot)
// <console>:60: error: value + is not a member of axle.quanta.UnittedQuantity[axle.quanta.Mass,Double]
//        (1d *: gram) + (2d *: foot)
//                     ^

Multiplication comes from spire’s Module typeclass:

(5.4 *: second) :* 100d
// res25: axle.quanta.UnittedQuantity[axle.quanta.Time,Double] = UnittedQuantity(540.0,UnitOfMeasurement(second,s,Some(http://en.wikipedia.org/wiki/Second)))

(32d *: century) :* (1d/3)
// res26: axle.quanta.UnittedQuantity[axle.quanta.Time,Double] = UnittedQuantity(10.666666666666666,UnitOfMeasurement(century,century,Some(http://en.wikipedia.org/wiki/Century)))

The methods over and by are used to multiply and divide other values with units. This behavior is not yet implemented.