# convert-units

[![Haskell](https://img.shields.io/badge/language-Haskell-orange.svg)](https://haskell.org) [![BSD3 License](https://img.shields.io/badge/license-BSD3-blue.svg)](https://github.com/AliceRixte/convert-units/LICENSE) [![Hackage](https://img.shields.io/hackage/v/convert-units.svg)](https://hackage.haskell.org/package/convert-units) [![Nightly](https://www.stackage.org/package/convert-units/badge/nightly)](https://www.stackage.org/nightly/package/convert-units) [![LTS](https://www.stackage.org/package/convert-units/badge/lts)](https://www.stackage.org/lts/package/convert-units)

A Haskell library to convert between units, that will statically check the dimensions of the units being converted.

Conversions are usually as fast as manual multiplication by a conversion factor, thanks to heavy use of inlining.

## Usage

You will need the `TypeApplications` extension:

``` haskell
>>> :set -XTypeApplications
```

### Convert between units

You can use `to` or  `fromTo` for conversions:
``` haskell
>>> t = Hour 8
>>> to @Minute t
Minute 480.0

>>> fromTo @Hour @Minute 8
Minute 480.0
```

User-friendly static errors when  trying to convert between incompatible dimensions:

```haskell
>>> fromTo @Minute @Meter 1
• Cannot convert unit ‘min’ to unit ‘m’ because their dimensions do not match.
      Dimension of ‘min’ is: T
      Dimension of ‘m’ is: L
```

There are two sorts of unit conversions:

1. The regular ones
``` haskell
>>> fromTo @Celsius @Kelvin 0
Kelvin 273.15
```

2. Conversion that only takes the conversion factor into account (and not potential offsets):
``` haskell
>>> fromTo' @Celsius @Kelvin 0
Kelvin 0.0
```

### Pretty printing

``` haskell
>>> putQuantity (Celsius 25)
25 °C
>>> putQuantity (quantity @(Kilo Meter ./. Hour) 130)
130 km⋅hr⁻¹
```

Get info about some unit:

``` haskell
>>> putInfoU @Newton
Unit:       Newton
 abbr:      N
Dimension:  Mass .*. Length .*. Time.^-2
 abbr:      M⋅L⋅T⁻²
Normalized: Kilo Gram .*. Meter .*. Second.^-2
 abbr:      kg⋅m⋅s⁻²
```

### Unit arithmetics

Multiplication by a scalar:

``` haskell
>>> 2 * Meter 4
Meter 8
```

You can multiply or divide two units:

``` haskell
>>> putQuantity $ Newton 1 .*. Meter 2
2 N⋅m
```

``` haskell
>>> v = Kilo (Meter 10) ./. Hour 2
>>> putQuantity v
5.0 km⋅hr⁻¹
>>> putQuantity $ to @(Meter ./. Second) v
1.3888888888888888 m⋅s⁻¹
```

Automatically simplify units by converting the right unit :
``` haskell
>>> putQuantity $ Kilo (Meter 2) .*~  Meter 3
6.0e-3 km²
```

or the left unit:

``` haskell
>>> putQuantity $ Kilo (Meter 2) ~*.  Meter 3
6000.0 m²
```

### Convert to and from SI base units

``` haskell
>>> v = toBaseUnit (quantity @(Kilo Meter ./. Hour) 36)
>>> putQuantity v
10.0 m⋅s⁻¹
```

### Make your own units, prefixes and dimensions

Make a new dimension with its associated base unit:

``` haskell
$(mkDim "Angle" "A" 1000)
$(mkBaseUnit "Radian" "rad" ''Angle)
```

Make a new unit convertible by multiplying with some  factor:
``` haskell
$(mkUnit "Minute" "min" ''Time 60)
```

Make a new prefix:

``` haskell
$(mkPrefix "Micro" "µ" 1e-6)
```

Make a new unit with special conversion:

``` haskell
$(mkUnitNoFactor "Fahrenheit" "°F" ''Temperature)

instance Fractional a => ConversionFactor Fahrenheit a where
  factor = 5 / 9
  {-# INLINE factor #-}

instance Fractional a => ConvertibleUnit Fahrenheit a where
  toBaseUnit (Fahrenheit x) = Kelvin ((x + 459.67) * 5 / 9)
  {-# INLINE toBaseUnit #-}

  fromBaseUnit (Kelvin x) = Fahrenheit (x * 9 / 5 - 459.67)
  {-# INLINE fromBaseUnit #-}
```

## Comparison with other Haskell unit libraries

There are other excellent units libraries out there, the two most used being:
- [dimensional](https://hackage.haskell.org/package/dimensional)
- [units](https://hackage.haskell.org/package/units)

Compared to these two libraries, `convert-units` offers

* Greater flexibility for conversions that do not use conversion factors, for instance for logarithmic units (see logarithmic pitch units in `Data.Unit.NonStd.Frequency` for instance)
* The possibility to add dimensions, such as `Angle`, `Information` (not yet implemented, see this [wikipedia article](https://en.wikipedia.org/wiki/Quantities_of_information)), and so on ...

| Feature                              | convert-units | dimensional | units |
|---------------------------------------|:------------:|:-----------:|:-----:|
| Static dimension checking             |      ✅     |     ✅      |  ✅   |
| Custom unit                           |      ✅     |     ✅      |  ✅   |
| Custom prefixes                       |      ✅     |     ✅      |  ✅   |
| Custom dimensions                     |      ✅     |     ❌      |  ❌   |
| Pretty-printing units                 |      ✅     |     ✅      |  ✅   |
| Offset-aware conversions (e.g. °C/K)  |      ✅     |     ✅      |  ❌   |
| Any conversion                        |      ✅     |     ❌      |  ❌   |
