Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/meadsteve/unit_fun
📏 Dimension based safety in elixir
https://github.com/meadsteve/unit_fun
conversion elixir types
Last synced: about 2 months ago
JSON representation
📏 Dimension based safety in elixir
- Host: GitHub
- URL: https://github.com/meadsteve/unit_fun
- Owner: meadsteve
- License: mit
- Created: 2015-11-26T09:13:17.000Z (about 9 years ago)
- Default Branch: master
- Last Pushed: 2018-09-12T13:13:02.000Z (over 6 years ago)
- Last Synced: 2024-10-11T03:11:12.231Z (2 months ago)
- Topics: conversion, elixir, types
- Language: Elixir
- Homepage:
- Size: 93.8 KB
- Stars: 21
- Watchers: 4
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
- freaking_awesome_elixir - Elixir - Attempt to add units to numbers in elixir to give some added type safety when dealing with numeric quantities. (Text and Numbers)
- fucking-awesome-elixir - unit_fun - Attempt to add units to numbers in elixir to give some added type safety when dealing with numeric quantities. (Text and Numbers)
- awesome-elixir - unit_fun - Attempt to add units to numbers in elixir to give some added type safety when dealing with numeric quantities. (Text and Numbers)
README
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 - Basic
First define some units:
```elixir
defmodule Pounds do
use UnitFun.Unit
enddefmodule Pence do
use UnitFun.Unit
end
```Then do something with them:
```elixir
use UnitFun.MathsOperators
import UnitFun.UnitTypesitem_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)
```## Example - Conversions
Conversions can be defined:
```elixir
defimpl UnitFun.Convertor, for: Pence do
def convert(_, Pounds, value), do: (value * 100)
enddefimpl 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 - Assertion
Errors can be raised if units aren't what they are expected to be:```elixir
UnitFun.assert_units(total_money, Miles)
```## Example - Composite units
New units can also be composed by multiplying existing units together:```elixir
use UnitFun.MathsOperators
import UnitFun.UnitTypeskm_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)
```## Example - Composite unit conversions
If there's a single unit way of representing some composite units this conversion can also be defined (N.B. there's currently no way of defining a conversion from a simple unit to a composite one):
```elixir
defimpl UnitFun.Convertor, for: UnitFun.ConvertorComplexTest.Pascals do
alias UnitFun.ConvertorComplexTest.Meters
alias UnitFun.ConvertorComplexTest.Newtons
alias UnitFun.Units.CompositeUnitdef convert(_, %CompositeUnit{numerators: [%Newtons{}], denominators: [%Meters{}]}, value) do
value
end
end
```## Example - Custom mathematic functions.
All the maths is controlled by protocols.
So for example if you decided pence should only be handled as integers (so rounding isn't an issue) the following
protocol could be defined:
```elixir
defimpl UnitFun.Maths.AddSubtractMaths, for: UnitFun.ExampleTest.Pence do
def add(_, left, right) when is_integer(left) and is_integer(right) do
left + right
end
def subtract(_, left, right) when is_integer(left) and is_integer(right) do
left - right
end
end
```
Now any addition using non integer quantities will raise a FunctionClauseError.
For convinience if nothing is defined then the kernel +-/* are used.## Example - Facts about units
It's possible to define units with facts that must always hold true. This is handled
by defining a list of functions that return true or false.```elixir
defmodule UnitFun.Examples.PositiveUnit do
@moduledoc false
use UnitFun.Unitdefp greater_than_zero(x), do: x >= 0
facts [
&greater_than_zero/1
]end
```Now whenever a `PositiveUnit` value is constructed the greater_than_zero callback will be executed.
If this returns False then an InvalidValueError will be raised.