quantities-0.2.0: Unit conversion and manipulation library.

Safe HaskellSafe-Inferred

Data.Quantities

Contents

Description

This package is used to create and manipulate physical quantities, which are a numerical value associated with a unit of measurement.

In this package, values with units are represented with the Quantity type. Included is an expression parser and a huge list of predefined quantities with which to parse strings into a Quantity datatype. Once created, a quantity can be converted to different units or queried for its dimensionality. A user can also operate on quantities arithmetically, and doing so uses automatic unit conversion and simplification.

Synopsis

Constructors

Currently, one constructor is supported to create quantities: fromString. There is an included expression parser that can parse values and strings corresponding to builtin units. To view defined unit types, look at the source code for defaultDefString.

fromString :: String -> Either QuantityError QuantitySource

Create a Quantity by parsing a string. Uses an UndefinedUnitError for undefined units. Handles arithmetic expressions as well.

>>> fromString "25 m/s"
Right 25.0 meter / second
>>> fromString "fakeunit"
Left (UndefinedUnitError "fakeunit")
>>> fromString "ft + 12in"
Right 2.0 foot

Make sure not to use dimensional quantities in exponents.

>>> fromString "m ** 2"
Right 1.0 meter ** 2.0
>>> fromString "m ** (2s)"
Left (ParserError "Used non-dimensionless exponent in ( Right 1.0 meter ) ** ( Right 2.0 second )")

unitsFromString :: String -> Either QuantityError CompoundUnitSource

Parse units from a string. Equivalent to fmap units . fromString

>>> unitsFromString "N * s"
Right [newton,second]

data Quantity Source

Combination of magnitude and units.

magnitude :: Quantity -> DoubleSource

Numerical magnitude of quantity.

>>> magnitude <$> fromString "100 N * m"
Right 100.0

units :: Quantity -> CompoundUnitSource

Units associated with quantity.

>>> units <$> fromString "3.4 m/s^2"
Right [meter,second ** -2.0]

Conversion

These functions are used to convert quantities from one unit type to another.

convert :: Quantity -> CompoundUnit -> Either QuantityError QuantitySource

Convert quantity to given units.

>>> convert <$> fromString "m" <*> unitsFromString "ft"
Right (Right 3.280839895013123 foot)

convertBase :: Quantity -> QuantitySource

Convert a quantity to its base units.

>>> convertBase <$> fromString "newton"
Right 1000.0 gram meter / second ** 2.0

dimensionality :: Quantity -> CompoundUnitSource

Computes dimensionality of quantity.

>>> dimensionality <$> fromString "newton"
Right [length,mass,time ** -2.0]

Quantity arithmetic

Once created, quantities can be manipulated using the included arithmetic functions.

>>> let (Right x) = fromString "m/s"
>>> let (Right y) = fromString "mile/hr"
>>> x `multiplyQuants` y
1.0 meter mile / hour / second
>>> x `divideQuants` y
1.0 hour meter / mile / second
>>> x `addQuants` y
Right 1.4470399999999999 meter / second
>>> x `subtractQuants` y
Right 0.55296 meter / second
>>> x `exptQuants` 1.5
1.0 meter ** 1.5 / second ** 1.5

The functions multiplyQuants, divideQuants, and exptQuants change units, and the units of the result are reduced to simplest terms.

>>> x `divideQuants` x
1.0
>>> fmap (multiplyQuants x) $ fromString "s"
Right 1.0 meter
>>> x `exptQuants` 0
1.0

addQuants :: Quantity -> Quantity -> Either QuantityError QuantitySource

Adds two quantities. Second quantity is converted to units of first quantity.

subtractQuants :: Quantity -> Quantity -> Either QuantityError QuantitySource

Subtract two quantities. Second quantity is converted to units of first quantity.

multiplyQuants :: Quantity -> Quantity -> QuantitySource

Multiplies two quantities.

divideQuants :: Quantity -> Quantity -> QuantitySource

Divides two quantities.

exptQuants :: Quantity -> Double -> QuantitySource

Exponentiates a quantity with a double.

Custom definitions

You don't have to use the default definitions provided by defaultDefString. Here is an example of adding a new unit called metric_foot.

 myDefString = defaultDefString ++ "\n" ++ "metric_foot = 300mm"
 (Right d') = readDefinitions myDefString
 myFromString = fromString' d'
>>> myFromString "metric_foot"
Right 1.0 metric_foot
>>> convertBase <$> myFromString "metric_foot"
Right 0.3 meter

It is usually much easier to copy the source code for defaultDefString and add your definitions in the appropriate spot (for example, put metric_foot next to the other unit definitions). Then, use fromString' to create your Quantity constructor.

NOTE: It is very important not to perform conversions on two quantities from different Definitions. Most of the error checking for undefined units is done when a unit is created, and not when performing conversions. We try to catch when different definitions are used.

 (Right m)  = fromString "m"
 (Right ft) = myFromString "ft"
>>> convert m (units ft)
Left (DifferentDefinitionsError meter foot)

fromString' :: Definitions -> String -> Either QuantityError QuantitySource

Create quantities with custom definitions.

 (Right d) = readDefinitions myDefString
 myFromString = fromString' d
>>> myFromString "25 m/s"
Right 25.0 meter / second

readDefinitions :: String -> Either QuantityError DefinitionsSource

Convert string of definitions into Definitions structure. See source code for defaultDefString for an example.

defaultDefString :: StringSource

View the source code for this declaration to see what units and prefixes are defined by default.

This string holds the definitions for units and prefixes. Base units are defined by the name of the unit, the name of the base in brackets, and any aliases for the unit after that, all separated by equal signs: meter = [length] = m. Prefixes are defined by placing a dash after all identifiers, and providing a value for the prefix: milli- = 1e-3 = m-. Other units are defined by using previously defined units in an expression: minute = 60 * second = min.

The reason these definitions aren't placed in a text file is so you don't have to operate your whole program in the IO monad. Theoretically, a user of this package can create their own definitions file or modify this one, but a mechanism for doing so hasn't been created yet.

These definitions are taken almost verbatim from the Pint unit conversion library for the Python programming language. Check them out on GitHub.

Error type

data QuantityError Source

Custom error type

Constructors

UndefinedUnitError String

Used when trying to parse an undefined unit.

DimensionalityError CompoundUnit CompoundUnit

Used when converting units that do not have the same dimensionality (example: convert meter to second).

UnitAlreadyDefinedError String

Used internally when defining units and a unit is already defined.

PrefixAlreadyDefinedError String

Used internally when defining units and a prefix is already defined.

ParserError String

Used when a string cannot be parsed.

DifferentDefinitionsError CompoundUnit CompoundUnit

Used when two quantities come from different Definitions.

type QuantityComputation = Either QuantityErrorSource

Useful for monadic computations with QuantityErrors. Some examples:

 computation :: QuantityComputation Quantity
 computation = do
   x <- fromString "mile/hr"
   y <- unitsFromString "m/s"
   convert x y

Returns Right 0.44704 meter / second

 computation :: QuantityComputation Quantity
 computation = do
   x <- fromString "BADUNIT"
   convertBase x

Returns Left (UndefinedUnitError BADUNIT)