ianmackenzie/elm-units-interval/2.2.1

NOTE: the latest version is 3.2.0

Quantity.Interval

This modules contains most of the core functionality for creating and working with Interval values.

Note: most examples assume that you have imported this module as

import Quantity.Interval as Interval
type Interval number units

Represents a finite, closed interval with a minimum and maximum value.

The two type parameters match those of the Quantity type from elm-units. For example, an Interval Int Pixels might represent a value between 200 and 300 pixels, and an Interval Float Meters might represent a value between 2.5 and 3.7 centimeters. (As with the Quantity type, the units type parameter refers to the base unit of a particular quantity type; lengths in meters, centimeters, feet, miles etc. are all internally stored in meters.)

Constructors

Quantity number units
-> Quantity number units
-> Interval number units

Construct an interval containing the two given values (which can be provided in either order).

-- "The heights of people participating in the study
-- ranged from 1.2 to 1.9 meters"
heightRange =
    Interval.from
        (Length.meters 1.2)
        (Length.meters 1.9)


-- "Please allow 4 to 6 weeks for delivery"
estimatedShippingTime =
    Interval.from
        (Duration.weeks 4)
        (Duration.weeks 6)
( Quantity number units, Quantity number units )
-> Interval number units

Construct an interval from its endpoints (the minimum and maximum values of the interval).

highwayCarSpeeds =
    Interval.fromEndpoints
        ( Speed.kilometersPerHour 90
        , Speed.kilometersPerHour 130
        )

The two values should be given in order but will be swapped if necessary to ensure a valid interval is returned:

Interval.endpoints <|
    Interval.fromEndpoints
        ( Speed.kilometersPerHour 130
        , Speed.kilometersPerHour 90
        )
--> ( Speed.kilometersPerHour 90
--> , Speed.kilometersPerHour 130
--> )
singleton : Quantity number units -> Interval number units

Construct a zero-width interval containing a single value.

Interval.singleton (Length.meters 3)
--> Interval.fromEndpoints
-->     ( Length.meters 3
-->     , Length.meters 3
-->     )

Booleans

Interval number units
-> Interval number units
-> Interval number units

Construct an interval containing both of the given intervals.

firstInterval =
    Interval.from (Length.feet 1) (Length.feet 2)


secondInterval =
    Interval.from (Length.feet 3) (Length.feet 6)


Interval.union firstInterval secondInterval
--> Interval.from (Length.feet 1) (Length.feet 6)

(Note that this is not strictly speaking a 'union' in the precise mathematical sense, since the result will contain values that are in between the two given intervals and not actually in either of them if those two intervals do not overlap.)

Interval number units
-> Interval number units
-> Maybe (Interval number units)

Attempt to construct an interval containing all the values common to both given intervals. If the intervals do not intersect, returns Nothing.

Interval.intersection
    (Interval.from (Mass.grams 1) (Mass.grams 3))
    (Interval.from (Mass.grams 2) (Mass.grams 5))
--> Just (Interval.from (Mass.grams 2) (Mass.grams 3))


Interval.intersection
    (Interval.from (Mass.grams 1) (Mass.grams 3))
    (Interval.from (Mass.grams 4) (Mass.grams 7))
--> Nothing

If the two intervals just touch, a singleton interval will be returned:

Interval.intersection
    (Interval.from (Mass.grams 1) (Mass.grams 3))
    (Interval.from (Mass.grams 3) (Mass.grams 5))
--> Just (Interval.singleton (Mass.grams 3))

Hull

These functions let you construct an Interval containing one or more input values.

Quantity number units
-> List (Quantity number units)
-> Interval number units

Find the interval containing one or more input values, by passing the first value as the first argument and then a list of all other values as the second argument. For example, to find the interval containing the values 5 cm, 2 cm, 3 cm and 4 cm:

Interval.hull
    (Length.centimeters 5)
    [ Length.centimeters 3
    , Length.centimeters 2
    , Length.centimeters 4
    ]
--> Interval.from
-->     (Length.centimeters 2)
-->     (Length.centimeters 5)

Why pass the first and all other values as separate arguments? It lets this function return an Interval instead of a Maybe Interval. If there was just a single list as an argument, then this function would have to handle the case of an empty list being passed by returning Nothing. As a result, when using this function you often end up using it within a case expression:

case values of
    [] ->
        -- some default behavior


    first :: rest ->
        let
            interval =
                Interval.hull first rest
        in
        -- normal behavior using 'interval'

If you do want the simpler behavior of taking a single list and returning a Maybe Interval, check out hullN.

hullN : List (Quantity number units) -> Maybe (Interval number units)

Attempt to construct an interval containing all N values in the given list. If the list is empty, returns Nothing. If you know you have at least one value, you can use hull instead.

Interval.hullN
    [ Duration.hours 2
    , Duration.hours 1
    , Duration.hours 3
    ]
--> Just <|
-->     Interval.from
-->         (Duration.hours 1)
-->         (Duration.hours 3)


Interval.hullN [ Duration.hours 5]
--> Just (Interval.singleton (Duration.hours 5))


Interval.hullN []
--> Nothing
hullOf : (a -> Quantity number units) -> a -> List a -> Interval number units

Like hull, but lets you work on any kind of item as long as a Quantity of some kind can be extracted from it. For example, if you had

type alias Person =
    { name : String
    , age : Duration
    }

then given some people you could find their range of ages as an Interval Float Seconds using

Interval.hullOf .age
    firstPerson
    [ secondPerson
    , thirdPerson
    , fourthPerson
    ]

See also hullOfN.

(a -> Quantity number units)
-> List a
-> Maybe (Interval number units)

Combination of hullOf and hullN.

Quantity number units
-> Quantity number units
-> Quantity number units
-> Interval number units

Construct an interval containing the three given values;

Interval.hull3 a b c

is equivalent to

Interval.hull a [ b, c ]

but is more efficient. (If you're looking for a hull2 function, from should do what you want.)

Aggregation

These functions let you 'aggregate' one or more intervals into a single larger interval that contains all of them.

Interval number units
-> List (Interval number units)
-> Interval number units

Construct an interval containing one or more given intervals:

Interval.aggregate
    (Interval.singleton (Length.feet 2))
    [ Interval.from
        (Length.feet 3)
        (Length.feet 4)
    ]
--> Interval.from (Length.feet 2) (Length.feet 4)

Works much like hull. See also aggregateN.

aggregateN : List (Interval number units) -> Maybe (Interval number units)

Attemp to construct an interval containing all of the intervals in the given list. If the list is empty, returns Nothing. If you know you have at least one interval, you can use aggregate instead.

aggregateOf : (a -> Interval number units) -> a -> List a -> Interval number units

Like aggregate, but lets you work on any kind of item as long as an interval can be generated from it (similar to hullOf).

(a -> Interval number units)
-> List a
-> Maybe (Interval number units)

Combination of aggregateOf and aggregateN.

Interval number units
-> Interval number units
-> Interval number units
-> Interval number units

Special case of aggregate for the case of three intervals;

Interval.aggregate3 first second third

is equivalent to

Interval.aggregate first [ second, third ]

but is more efficient. (If you're looking for an aggregate2 function, union should do what you want.)

Properties

Interval number units
-> ( Quantity number units, Quantity number units )

Get the endpoints of an interval (its minimum and maximum values) as a tuple. The first value will always be less than or equal to the second.

( minValue, maxValue ) =
    Interval.endpoints someInterval

For any interval,

Interval.endpoints interval

is equivalent to (but more efficient than)

( Interval.minValue interval
, Interval.maxValue interval
)
minValue : Interval number units -> Quantity number units

Get the minimum value of an interval.

maxValue : Interval number units -> Quantity number units

Get the maximum value of an interval.

midpoint : Interval Float units -> Quantity Float units

Get the midpoint of an interval.

Interval.midpoint <|
    Interval.from
        (Duration.hours 2)
        (Duration.hours 3)
--> Duration.hours 2.5
width : Interval number units -> Quantity number units

Get the width of an interval.

Interval.width <|
    Interval.from
        (Length.meters 1.2)
        (Length.meters 1.35)
--> Length.centimeters 15

Queries

contains : Quantity number units -> Interval number units -> Bool

Check if an interval contains a given value:

angleInterval =
    Interval.from
        (Angle.degrees -10)
        (Angle.degrees 30)


angleInterval |> Interval.contains (Angle.degrees 0)
--> True


angleInterval |> Interval.contains (Angle.degrees 45)
--> False

The minimum and maximum values of an interval are considered to be contained in the interval (but be careful of numerical roundoff):

angleInterval |> Interval.contains (Angle.degrees 30)
--> True
isContainedIn : Interval number units -> Interval number units -> Bool

Check if the second interval is fully contained in the first.

angleInterval =
    Interval.from
        (Angle.degrees -30)
        (Angle.degrees 30)


Interval.from (Angle.degrees -5) (Angle.degrees 15)
    |> Interval.isContainedIn angleInterval
--> True


Interval.from (Angle.degrees 15) (Angle.degrees 45)
    |> Interval.isContainedIn angleInterval
--> False

Be careful with the argument order! If not using the |> operator, the first example would be written as:

Interval.isContainedIn angleInterval
    (Interval.from
        (Angle.degrees -5)
        (Angle.degrees 15)
    )
--> True
intersects : Interval number units -> Interval number units -> Bool

Check if two intervals touch or overlap (have any values in common).

distanceInterval =
    Interval.from
        (Length.kilometers 5)
        (Length.kilometers 10)


distanceInterval
    |> Interval.intersects
        (Interval.from
            (Length.kilometers 8)
            (Length.kilometers 12)
        )
--> True


distanceInterval
    |> Interval.intersects
        (Interval.from
            (Length.kilometers 12)
            (Length.kilometers 15)
        )
--> False

Intervals that just touch each other are considered to intersect (this is consistent with intersection which will return a zero-width interval for the intersection of two just-touching intervals):

distanceInterval
    |> Interval.intersects
        (Interval.from
            (Length.kilometers 10)
            (Length.kilometers 15)
        )
--> True
isSingleton : Interval number units -> Bool

Check if the interval is a singleton (the minimum and maximum values are the same).

Interval.isSingleton <|
    Interval.from (Length.meters 2) (Length.meters 2)
--> True


Interval.isSingleton <|
    Interval.from (Length.meters 2) (Length.meters 3)
--> False

Interpolation

interpolate : Interval Float units -> Float -> Quantity Float units

Interpolate between an interval's endpoints based on a parameter value. A value of 0.0 corresponds to the minimum value of the interval, a value of 0.5 corresponds to its midpoint and a value of 1.0 corresponds to its maximum value:

lengthInterval =
    Interval.from (Length.meters 1) (Length.meters 5)


Interval.interpolate lengthInterval 0
--> Length.meters 1


Interval.interpolate lengthInterval 0.75
--> Length.meters 4

Values less than 0.0 or greater than 1.0 can be used to extrapolate:

Interval.interpolate lengthInterval 1.5
--> Length.meters 7

Note that because of how Interval.from works, the interpolation is in fact from the minimum value to the maximum, not "from the first Interval.from argument to the second":

Interval.interpolate
    (Interval.from
        (Length.meters 0)
        (Length.meters 10)
    )
    0.2
--> 2


Interval.interpolate
    (Interval.from
        (Length.meters 10)
        (Length.meters 0)
    )
    0.2
--> 2 -- not 8!

If you want to interpolate from one number down to another, you can use Quantity.interpolateFrom from the elm-units package.

interpolationParameter : Interval Float units -> Quantity Float units -> Float

Given an interval and a given value, determine the corresponding interpolation parameter (the parameter that you would pass to interpolate to get the given value):

Interval.interpolationParameter
    (Interval.from
        (Duration.minutes 10)
        (Duration.minutes 15)
    )
    (Duration.minutes 12)
--> 0.4

The result will be between 0 and 1 if (and only if) the given value is contained in the given interval:

Interval.interpolationParameter
    (Interval.from
        (Duration.minutes 10)
        (Duration.minutes 15)
    )
    (Duration.minutes 18)
--> 1.6


Interval.interpolationParameter
    (Interval.from
        (Duration.minutes 10)
        (Duration.minutes 15)
    )
    (Duration.minutes 9)
--> -0.2

This is the inverse of interpolate; for any non-zero-width interval,

Interval.interpolationParameter interval value
    |> Interval.interpolate interval

should be equal to the original value (within numerical roundoff).

Arithmetic

These functions let you do math with Interval values, following the rules of interval arithmetic.

negate : Interval number units -> Interval number units

Negate an interval. Note that this will flip the order of the endpoints.

Interval.negate <|
    Interval.from
        (Angle.degrees 20)
        (Angle.degrees 30)
--> Interval.from
-->     (Angle.degrees -30)
-->     (Angle.degrees -20)
add :
Quantity number units
-> Interval number units
-> Interval number units

Add the given amount to an interval:

lengthInterval =
    Interval.from
        (Length.meters 2)
        (Length.meters 3)


lengthInterval |> Interval.add (Length.centinmeters 20)
--> Interval.from
-->     (Length.meters 2.2)
-->     (Length.meters 3.2)
Quantity number units
-> Interval number units
-> Interval number units

Subtract the given amount from an interval.

angleInterval =
    Interval.from
        (Angle.degrees -10)
        (Angle.degrees 50)


angleInterval |> Interval.subtract (Angle.degrees 30)
--> Interval.from
-->     (Angle.degrees -40)
-->     (Angle.degrees 20)
multiplyBy : number -> Interval number units -> Interval number units

Multiply an interval by a given value:

Interval.multiplyBy 5 <|
    Interval.from
        (Duration.minutes 20)
        (Duration.minutes 30)
--> Interval.from
-->     (Duration.minutes 100)
-->     (Duration.minutes 150)

Note that this will flip the order of the interval's endpoints if the given value is negative:

Interval.multiplyBy -2 <|
    Interval.from
        (Angle.degrees 20)
        (Angle.degrees 30)
--> Interval.from
-->     (Angle.degrees -60)
-->     (Angle.degrees -40)
divideBy : Float -> Interval Float units -> Interval Float units

Divide an interval by a given value:

Interval.divideBy 2 <|
    Interval.from (Length.feet 2) (Length.feet 3)
--> Interval.from (Length.feet 1) (Length.feet 1.5)

Note that this will flip the order of the interval's endpoints if the given value is negative:

Interval.divideBy -2 <|
    Interval.from
        (Angle.degrees 20)
        (Angle.degrees 30)
--> Interval.from
-->     (Angle.degrees -15)
-->     (Angle.degrees -10)
half : Interval Float units -> Interval Float units

Shorthand for multiplyBy 0.5.

twice : Interval number units -> Interval number units

Shorthand for multiplyBy 2.

Interval number units
-> Interval number units
-> Interval number units

Add two intervals together.

firstInterval =
    Interval.from (Length.feet 5) (Length.feet 10)


secondInterval =
    Interval.from (Length.feet 2) (Length.feet 3)


firstInterval |> Interval.plus secondInterval
--> Interval.from (Length.feet 7) (Length.feet 13)
Interval number units
-> Interval number units
-> Interval number units

Subtract the first interval from the second. This means that minus makes the most sense when using |>:

firstInterval =
    Interval.from (Length.feet 5) (Length.feet 10)


secondInterval =
    Interval.from (Length.feet 2) (Length.feet 3)


firstInterval |> Interval.minus secondInterval
--> Interval.from (Length.feet 2) (Length.feet 8)

Without the pipe operator, the above would be written as:

Interval.minus secondInterval firstInterval
--> Interval.from 2 8
Interval number units2
-> Interval number units1
-> Interval number (Product units1 units2)

Multiply the second interval by the first. The order only matters if the two intervals have different units (since it will affect the units of the result) but, like other functions in this module, times is generally designed to be used with |>:

width =
    Interval.from (Length.centimeters 10) (Length.centimeters 12)


height =
    Interval.from (Length.centimeters 5) (Length.centimeters 6)


width |> Interval.times height
--> Interval.from
-->     (Area.squareCentimeters 50)
-->     (Area.squareCentimeters 72)

Random value generation

randomValue : Interval Float units -> Generator (Quantity Float units)

Create a random generator for quantities within a given interval. For example,

Interval.randomValue <|
    Interval.from (Length.meters 5) (Length.meters 10)

is a Generator that will produce random lengths between 5 and 10 meters.