UnitFun
=======
[![Build Status](https://travis-ci.org/meadsteve/unit_fun.svg)](https://travis-ci.org/meadsteve/unit_fun)
Attempt to add units to numbers in elixir to give some added type saftey when dealing with numeric quantities.
## Why?
One good example: pounds(dollars) should never be accidentally added to pence(cents) without conversion. Both are numeric. Wrapping the numeric data in a tuple with unit information seems like a good idea. This library gives a neat way of expressing this.
## Example
First define some units:
```elixir
defmodule Pounds do
use UnitFun.Unit
end
defmodule Pence do
use UnitFun.Unit
end
```
Then do something with them:
```elixir
use UnitFun.MathsOperators
import UnitFun.UnitTypes
item_cost = 5 <~ Pounds # UnitFun.with_units(5, Pounds)
item_tax = 100 <~ Pence # UnitFun.with_units(100, Pence)
# The following will throw an error as the units mismatch:
item_cost + item_tax # UnitFun.add(item_cost, item_tax)
```
Conversions can be defined:
```elixir
defimpl UnitFun.Convertor, for: Pence do
def convert(_, Pounds, value), do: (value * 100)
end
defimpl UnitFun.Convertor, for: Pounds do
#Note: please don't actually do a divison for any financial maths
# You're going to lose data and have a bad time.
def convert(_, Pence, value), do: (value / 100)
end
```
And now the following:
```elixir
# returns: %UnitFun.Value{value: 6, units: Pounds}
total = item_cost + item_tax # UnitFun.add(item_cost, item_tax)
# returns: %UnitFun.Value{value: 600, units: Pence}
total_in_pence = total <~ Pence # UnitFun.with_units(total, Pence)
```
## Example squared
New units can also be composed by multiplying existing units together:
```elixir
use UnitFun.MathsOperators
import UnitFun.UnitTypes
km_squared = Kilometers * Kilometers # UnitFun.multiply(Kilometers, Kilometers)
```
These newly defined units can then be used as with all previous examples
```elixir
edge = 4 <~ Kilometers # UnitFun.with_units(4, Kilometers)
area = edge * edge # UnitFun.multiply(edge, edge)
expected_area = 16 <~ km_squared # UnitFun.with_units(16, km_squared)
assert area == expected_area # UnitFun.equals(area, expected_area)
```
Dividing/multiplying by united types returns values with new types so correctness can be asserted on.
```elixir
miles_per_hour = Miles / Hours # UnitFun.divide(Miles, Hours)
speed = 40 <~ miles_per_hour # UnitFun.with_units(40, miles_per_hour)
time_spent_travelling = 2 <~ Hours # UnitFun.with_units(2, hours)
#the distance will be in Miles as the hours cancel out
distance_travelled_in_two_hours = time_spent_travelling * speed # UnitFun.multiply(time_spent_travelling, speed)
assert distance_travelled_in_two_hours == 80 <~ Miles # UnitFun.with_units(80, Miles)
```