ianmackenzie/elm-units/1.0.0

NOTE: the latest version is 2.10.0

Quantity

type Quantity number units
= Quantity number

A Quantity is effectively a number (an Int or Float) tagged with a units type. So a

Quantity Float Meters

is a Float number of Meters and a

Quantity Int Pixels

is an Int number of Pixels. When compiling with elm make --optimize the Quantity wrapper type will be compiled away, so the runtime performance should be comparable to using a raw Float or Int.

Unit types

The Squared and Rate units types allow you to build up and work with composite units in a fairly flexible way.

type Squared units

Represents a units type that is the square of some other units type; for example, Meters is one units type (the units type of a Length) and Squared Meters is another (the units type of an Area). This is useful because some functions in this module (specifically product, squared, and sqrt) "know" about the Squared type and how to work with it.

type Rate dependentUnits independentUnits

Represents the units type of a rate or quotient such as a speed (Rate Meters Seconds) or a pressure (Rate Newtons SquareMeters). As with Squared, there are several functions that "know" about the Rate units type and how to work with it - see Working with rates for details.

Constants

zero : Quantity number units

A generic zero value. This can be treated as either an Int or Float quantity in any units type, similar to how Nothing can be treated as any kind of Maybe type and [] can be treated as any kind of List.

infinity : Quantity Float units

Alias for positiveInfinity.

A generic positive infinity value.

A generic negative infinity value.

Comparison

lessThan : Quantity number units -> Quantity number units -> Bool

Check if one quantity is less than another. Note the argument order!

oneMeter =
    Length.meters 1


Length.feet 1 |> Quantity.lessThan oneMeter
--> True


-- Same as:
Quantity.lessThan oneMeter (Length.feet 1)
--> True


List.filter (Quantity.lessThan oneMeter)
    [ Length.feet 1
    , Length.parsecs 1
    , Length.yards 1
    , Length.lightYears 1
    ]
--> [ Length.feet 1, Length.yards 1 ]
greaterThan : Quantity number units -> Quantity number units -> Bool

Check if one quantity is greater than another. Note the argument order!

oneMeter =
    Length.meters 1


Length.feet 1 |> Quantity.greaterThan oneMeter
--> False


-- Same as:
Quantity.greaterThan oneMeter (Length.feet 1)
--> False


List.filter (Quantity.greaterThan oneMeter)
    [ Length.feet 1
    , Length.parsecs 1
    , Length.yards 1
    , Length.lightYears 1
    ]
--> [ Length.parsecs 1, Length.lightYears 1 ]
compare : Quantity number units -> Quantity number units -> Order

Compare two quantities, returning an Order value indicating whether the first is less than, equal to or greater than the second.

Quantity.compare (Duration.minutes 90) (Duration.hours 1)
--> GT


Quantity.compare (Duration.minutes 60) (Duration.hours 1)
--> EQ
Quantity number units
-> Quantity number units
-> Quantity number units
-> Bool

Check if two quantities are equal within a given absolute tolerance. The given tolerance must be greater than or equal to zero - if it is negative, then the result will always be false.

-- 3 feet is 91.44 centimeters or 0.9144 meters


Quantity.equalWithin (Length.centimeters 10)
    (Length.meters 1)
    (Length.feet 3)
--> True


Quantity.equalWithin (Length.centimeters 5)
    (Length.meters 1)
    (Length.feet 3)
--> False
max :
Quantity number units
-> Quantity number units
-> Quantity number units

Find the maximum of two quantities.

Quantity.max (Duration.hours 2) (Duration.minutes 100)
--> Duration.hours 2
min :
Quantity number units
-> Quantity number units
-> Quantity number units

Find the minimum of two quantities.

Quantity.min (Duration.hours 2) (Duration.minutes 100)
--> Duration.minutes 100
isNaN : Quantity Float units -> Bool

Check if a quantity's underlying value is NaN (not-a-number).

Quantity.isNan (Quantity.sqrt (Area.squareMeters -4))
--> True


Quantity.isNan (Quantity.sqrt (Area.squareMeters 4))
--> False
isInfinite : Quantity Float units -> Bool

Check if a quantity is positive or negative infinity.

Quantity.isInfinite
    (Length.meters 1
        |> Quantity.per (Duration.seconds 0)
    )
--> True


Quantity.isInfinite Quantity.negativeInfinity
--> True

Arithmetic

negate : Quantity number units -> Quantity number units

Negate a quantity!

Quantity.negate (Length.millimeters 10)
--> Length.millimeters -10
Quantity number units
-> Quantity number units
-> Quantity number units

Add two quantities.

Length.meters 1 |> Quantity.plus (Length.centimeters 5)
--> Length.centimeters 105
Quantity number units
-> Quantity number units
-> Quantity number units

Subtract one quantity from another. Note the argument order!

fifteenMinutes =
    Duration.minutes 15


Duration.hours 1 |> Quantity.minus fifteenMinutes
--> Duration.minutes 45


-- Same as:
Quantity.minus fifteenMinutes (Duration.hours 1)
--> Duration.minutes 45


List.map (Quantity.minus fifteenMinutes)
    [ Duration.hours 1
    , Duration.hours 2
    , Duration.minutes 30
    ]
--> [ Duration.minutes 45
--> , Duration.minutes 105
--> , Duration.minutes 15
--> ]
Quantity number units
-> Quantity number units
-> Quantity number (Squared units)

Multiply two quantities with the same units together, resulting in a quantity in Squared units.

This works for any units type (which is useful when used with sqrt!) but one special case is worth pointing out. The units type of an Area is SquareMeters, which is a type alias for Squared Meters. This means that the product of two Lengths does in fact give you an Area:

Quantity.product
    (Length.meters 2)
    (Length.centimeters 40)
--> Area.squareMeters 0.8


-- This is the definition of an acre, I kid you not 😈
Quantity.product (Length.feet 66) (Length.feet 660)
--> Area.acres 1

Note that there are other forms of multiplication!

ratio : Quantity Float units -> Quantity Float units -> Float

Find the ratio of two quantities with the same units.

Quantity.ratio (Length.miles 1) (Length.yards 1)
--> 1760
scaleBy : number -> Quantity number units -> Quantity number units

Multiply a Quantity by a number.

Quantity.scaleBy 1.5 (Duration.hours 1)
--> Duration.minutes 90

Note that there are other forms of multiplication!

divideBy : Float -> Quantity Float units -> Quantity Float units

Divide a Quantity by a Float.

Quantity.divideBy 2 (Duration.hours 1)
--> Duration.minutes 30
abs : Quantity number units -> Quantity number units

Get the absolute value of a quantity.

Quantity.abs (Duration.milliseconds -10)
--> Duration.milliseconds 10
Quantity number units
-> Quantity number units
-> Quantity number units
-> Quantity number units

Given a lower and upper bound, clamp a given quantity to within those bounds. Say you wanted to clamp an angle to be between +/-30 degrees:

lowerBound =
    Angle.degrees -30


upperBound =
    Angle.degrees 30


Quantity.clamp lowerBound upperBound (Angle.degrees 5)
--> Angle.degrees 5


-- One radian is approximately 57 degrees
Quantity.clamp lowerBound upperBound (Angle.radians 1)
--> Angle.degrees 30


Quantity.clamp lowerBound upperBound (Angle.turns -0.5)
--> Angle.degrees -30
squared : Quantity number units -> Quantity number (Squared units)

Square a quantity with some units, resulting in a new quantity in Squared units:

Quantity.squared (Length.meters 5)
--> Area.squareMeters 25

(See the documentation of product for an explanation of why a squared Length does in fact give you an Area.)

sqrt : Quantity Float (Squared units) -> Quantity Float units

Take a quantity in Squared units and return the square root of that quantity in plain units:

Quantity.sqrt (Area.hectares 1)
--> Length.meters 100

Getting fancier, you could write a 2D hypotenuse (magnitude) function that worked on any quantity type (length, speed, force...) as

hypotenuse :
    Quantity Float units
    -> Quantity Float units
    -> Quantity Float units
hypotenuse x y =
    Quantity.sqrt <|
        Quantity.sum
            [ Quantity.squared x
            , Quantity.squared y
            ]

This works because:

  • The x and y arguments are both in units
  • So each list item is in Squared units
  • So the sum is also in Squared units
  • And calling sqrt on something in Squared units returns a value back in units

Int/Float conversion

These functions only really make sense for quantities in units like pixels, cents or game tiles where an Int number of units is meaningful. For quantities like Length or Duration, it doesn't really make sense to round to an Int value since the underyling base unit is pretty arbitrary - should rounding a Duration give you an Int number of seconds, milliseconds, or something else?

round : Quantity Float units -> Quantity Int units

Round a Float-valued quantity to the nearest Int.

Quantity.round (Pixels.pixels 3.5)
--> Pixels.pixels 4
floor : Quantity Float units -> Quantity Int units

Round a Float-valued quantity down to the nearest Int.

Quantity.floor (Pixels.pixels 2.9)
--> Pixels.pixels 2


Quantity.floor (Pixels.pixels -2.1)
--> Pixels.pixels -3
ceiling : Quantity Float units -> Quantity Int units

Round a Float-valued quantity up to the nearest Int.

Quantity.ceiling (Pixels.pixels 1.2)
--> Pixels.pixels 2


Quantity.ceiling (Pixels.pixels -2.1)
--> Pixels.pixels -2
truncate : Quantity Float units -> Quantity Int units

Round a Float-valued quantity towards zero.

Quantity.truncate (Pixels.pixels -2.8)
--> Pixels.pixels -2
toFloatQuantity : Quantity Int units -> Quantity Float units

Convert a Quantity Int units to a Quantity Float units with the same value. Useful when you have an Int-valued quantity and want to divide it by something, multiply it by a fractional value etc.

List functions

These functions act just like the corresponding functions in the built-in List module. They're necessary because the built-in List.sum only supports List Int and List Float, and minimum/maximum/sort only support built-in comparable types like Int, Float, String and tuples.

sum : List (Quantity number units) -> Quantity number units

Find the sum of a list of quanties.

Quantity.sum
    [ Length.meters 1
    , Length.centimeters 2
    , Length.millimeters 3
    ]
--> Length.meters 1.023


Quantity.sum []
--> Quantity.zero
minimum : List (Quantity number units) -> Maybe (Quantity number units)

Find the minimum value in a list of quantities. Returns Nothing if the list is empty.

Quantity.minimum
    [ Mass.kilograms 1
    , Mass.pounds 2
    , Mass.tonnes 3
    ]
--> Just (Mass.pounds 2)
maximum : List (Quantity number units) -> Maybe (Quantity number units)

Find the maximum value in a list of quantities. Returns Nothing if the list is empty.

Quantity.maximum
    [ Mass.kilograms 1
    , Mass.pounds 2
    , Mass.tonnes 3
    ]
--> Just (Mass.tonnes 3)
sort : List (Quantity number units) -> List (Quantity number units)

Sort a list of quantities.

Quantity.sort
    [ Mass.kilograms 1
    , Mass.pounds 2
    , Mass.tonnes 3
    ]
--> [ Mass.pounds 2
--> , Mass.kilograms 1
--> , Mass.tonnes 3
--> ]

Working with rates

per :
Quantity Float independentUnits
-> Quantity Float dependentUnits
-> Quantity Float (Rate dependentUnits independentUnits)

Construct a rate of change by dividing a dependent quantity (numerator) by an independent quantity (denominator):

distance =
    Length.miles 1


time =
    Duration.minutes 1


speed =
    distance |> Quantity.per time


speed |> Speed.inMilesPerHour
--> 60

Note that we could directly use our rate of change value as a Speed! That is because many built-in quantity types are defined as rates of change, for example:

  • Speed is Length per Duration
  • Acceleration is Speed per Duration
  • Pressure is Force per Area
  • Power is Energy per Duration
  • Force is Energy per Length
  • Current is Charge per Duration
  • Voltage is Power per Current
  • Resistance is Voltage per Current
Quantity number independentUnits
-> Quantity number (Rate dependentUnits independentUnits)
-> Quantity number dependentUnits

Multiply a rate of change by an independent quantity (the denominator in the rate) to get a total value:

-- Pressure is force per area
pressure =
    Pressure.kilopascals 10


area =
    Area.squareMeters 3


pressure |> Quantity.times area
--> Force.newtons 30000

Note that there are other forms of multiplication!

at :
Quantity number (Rate dependentUnits independentUnits)
-> Quantity number independentUnits
-> Quantity number dependentUnits

Same as times but with the argument order flipped, which may read better in some cases:

Duration.minutes 30
    |> Quantity.at
        (Speed.kilometersPerHour 100)
--> Length.kilometers 50

Can be useful to define conversion functions from one unit to another, since if you define a rate then Quantity.at rate will give you a conversion function:

pixelDensity : Quantity Float (Rate Pixels Meters)
pixelDensity =
    Pixels.pixels 96 |> Quantity.per (Length.inches 1)


lengthToPixels : Length -> Quantity Float Pixels
lengthToPixels length =
    Quantity.at pixelDensity length


lengthToPixels (Length.inches 3)
--> Pixels.pixels 288

Eagle-eyed readers will note that using partial application you could also simply write

lengthToPixels =
    Quantity.at pixelDensity
at_ :
Quantity Float (Rate dependentUnits independentUnits)
-> Quantity Float dependentUnits
-> Quantity Float independentUnits

Given a rate and a dependent value, determine the necessary amount of the independent value:

Length.kilometers 75
    |> Quantity.at_
        (Speed.kilometersPerHour 100)
--> Duration.minutes 45

Where times and at perform multiplication, at_ performs division - you multiply a speed by a duration to get a distance, but you divide a distance by a speed to get a duration.

Similar to at, at_ can be used to define an inverse conversion function:

pixelDensity : Quantity Float (Rate Pixels Meters)
pixelDensity =
    Pixels.pixels 96 |> Quantity.per (Length.inches 1)


pixelsToLength : Quantity Float Pixels -> Length
pixelsToLength pixels =
    Quantity.at_ pixelDensity pixels


pixelsToLength (Pixels.pixels 48)
--> Length.inches 0.5
Quantity Float (Rate dependentUnits independentUnits)
-> Quantity Float (Rate independentUnits dependentUnits)

Find the inverse of a given rate. May be useful if you are using a rate to define a conversion, and want to convert the other way;

Quantity.at (Quantity.inverse rate)

is equivalent to

Quantity.at_ rate

Unitless quantities

It is sometimes useful to be able to represent unitless quantities, especially when working with generic code (in most other cases, it is likely simpler and easier to just use Int or Float values directly). All the conversions in this section simply wrap or unwrap a Float or Int value into a Quantity value, and so should get compiled away entirely when using elm make --optimize.

A special units type representing 'no units'. A Quantity Int Unitless value is interchangeable with a simple Int, and a Quantity Float Unitless value is interchangeable with a simple Float.

int : Int -> Quantity Int Unitless

Convert a plain Int into a Quantity Int Unitless value.

toInt : Quantity Int Unitless -> Int

Convert a Quantity Int Unitless value into a plain Int.

float : Float -> Quantity Float Unitless

Convert a plain Float into a Quantity Float Unitless value.

toFloat : Quantity Float Unitless -> Float

Convert a Quantity Float Unitless value into a plain Float.

If you're looking for a function to convert a Quantity Int units to Quantity Float units, check out toFloatQuantity.