lue-bird/elm-bounded-nat/22.0.0

NOTE: the latest version is 35.0.0

N

Natural numbers within a typed range.

type N range

A bounded natural number >= 0

argument type

-- ≥ 0, any limitations allowed
N range_


-- ≥ 4
N (In (Fixed (Add4 minMinus4_)) max_)


-- 4 ≤ n ≤ 15
N (In (Fixed (Add4 minMinus4_)) (Up maxTo15_ To N15))

In (Add4 minMinus4_) N15 says:

  • the minimum-constraint can be 4 + 0|4 + 1|4 + 2|...
  • the argument's maximum + some variable maxTo15 is 15 which means: the maximum is ≤ 15

result type

-- ≥ 4
N (Min (Up x To (Add4 x)))


-- 2 ≤ n ≤ 12
N (In (Up x To (Add2 x)) (Up x To (Add12 x)))


n3 : N (In (Up minX To (Add3 minX)) (Up maxX To (Add3 maxX)))

[Up low To high] is a representation as a difference of the limit high - low

This enables adding, subtracting. Consider it an implementation detail.

n3 |> N.add n6
--→ n9

stored type

what to put in declared types like Model

-- ≥ 4
N (Min (Fixed N4))


-- 2 ≤ n ≤ 12
N (In (Fixed N2) (Fixed N12))

They are like result types but type variables are set to N0.

more type examples at In, Min

bounds

type alias In minimumAsDifference maximumAsDifference =
RecordWithoutConstructorFunction
{ minimumAsDifference : minimumAsDifference
, maximumAsDifference : maximumAsDifference
}

somewhere within a minimum & maximum

   ↓ minimum   ↓ maximum
⨯ [✓ ✓ ✓ ✓ ✓ ✓ ✓] ⨯ ⨯ ⨯...

argument type in a range

-- number between 3 and 5
N (In (Fixed (Add3 minX_)) (Up maxTo5_ N5))


percent : N (In min_ (Up maxTo100 To N100)) -> Percent

min ≤ max ≤ N100

If you want a number where you just care about the minimum, leave max as a type variable

   ↓ minimum    ↓ maximum or  →
⨯ [✓ ✓ ✓ ✓ ✓ ✓ ✓...


-- any natural number
N (In min_ max_)

A number, at least 5:

N (In (Add5 minMinus5_) max_)

→ max_ could be a specific maximum or no maximum at all

result type in a range

n3 : N (In (Up x0 To (Add3 x0)) (Up x1 To (Add3 x1)))


between3And6 : N (In (Up minX To (Add3 minX)) (Up maxX To (Add6 maxX)))


between3And6 |> N.add n3
--: N (In (Up minX To (Add6 minX)) (Up maxX To (Add9 maxX)))

stored type in a range

Like the result type but every Up x To (Add<x> x) becomes Fixed N<x>

An example where this is useful using typesafe-array:

type Tree branchingFactor element
    = Tree
        element
        (ArraySized
            branchingFactor
            (Maybe (Tree branchingFactor element))
        )


type alias TreeBinary element =
    Tree (Exactly N2) element

Remember: ↑ and other Min/Exactly/Fixed are result/stored types, not argument types.


Do not use == on 2 values storing a range. It can lead to elm crashing because differences are stored as functions. compare instead.

type alias Min lowestPossibleValue =
In lowestPossibleValue MaxNo

Only stored / result types should use the type Min:

   ↓ minimum    ↓ or →
⨯ [✓ ✓ ✓ ✓ ✓ ✓ ✓...

result type without maximum constraint

Sometimes, you simply cannot compute a maximum.

abs : Int -> N (In (Up x To x) ??)
                ↓
abs : Int -> N (Min (Up x To x))


-- number ≥ 5
atLeast5 : N (Min (Up x To (Add5 x)))


atLeast5 |> N.minAdd n3
--: N (Min (Up x To (Add8 x)))

argument type without maximum constraint

Every Min min is of type In min ..., so using a type variable for the maximum on arguments is highly encouraged when no maximum constraint should be enforced.

-- any natural number
N (In min_ max_)


-- number, at least 5
N (In (Add5 minMinus5_) max_)

stored type without maximum constraint

Like the result type but every Up x To (Add<x> x) becomes Fixed N<x>

An example where this is useful using typesafe-array:

type Tree branchingFactor element
    = Tree
        element
        (ArraySized
            branchingFactor
            (Maybe (Tree branchingFactor element))
        )


type alias TreeMulti element =
    Tree (Min (Fixed N2)) element

Remember: ↑ and other Min/Exactly/Fixed are result/stored types, not argument types.


Do not use == on 2 values storing a range. It can lead to elm crashing because differences are stored as functions. compare instead.

type alias MaxNo =
Fixed { maximumUnknown : () }

Flag "The number's upper limit is unknown" used in the definition of Min:

type alias Min min =
    In minimum MaxNo
type alias Exactly n =
In (Fixed n) (Fixed n)

Allow only a specific number.

Useful as a stored & argument type in combination with typesafe-arrays, not with Ns.

byte : ArraySized (Exactly N8) Bit -> Byte

→ A given ArraySized must have exactly 8 Bits.

type alias TicTacToeBoard =
    ArraySized
        (Exactly N3)
        (ArraySized (Exactly N3) TicTacToField)

→ A given ArraySized must have exactly 3 by 3 TicTacToeFields.

type Up low toTag high
= Difference
{ up : low -> high
, down : high -> low
}

Up low To high: an exact number as the difference high - low.

type alias Down high toTag low =
Up low toTag high

Down high To low: an exact number as the difference high - low.

type To

Just a word in a difference type:

Up low To high


Down high To high

→ distance high - low.

type alias Fixed n =
Up N0 To n

The exact number as the difference from 0 to the number.

A stored type looks like a result type but every Up x To (Add<x> x) becomes Fixed N<x>

create

abs : Int -> N (Min (Up x To x))

The absolute value of an Int which is always ≥ 0

-4
    |> N.abs
    --: N (Min (Up x To x))
    |> N.toInt


--> 4
16 |> N.abs |> N.toInt --> 16

Really only use this if you want the absolute value.

badLength =
    List.length >> N.abs
  • maybe, there's a solution that never even theoretically deals with unexpected values:

    mostCorrectLength =
        List.foldl
            (\_ -> N.minAdd n1 >> N.minDown n1)
            (n0 |> N.maxNo)
  • other times, though, like with Array.length, which isn't O(n), you can escape with for example

    arrayLength =
        Array.length >> N.intAtLeast n0
( N
(In
lowerLimitMin
(Up lowerLimitMaxToUpperLimitMin_ To upperLimitMin)
)
, N (In (Fixed upperLimitMin) upperLimitMax)
)
-> Generator (N (In lowerLimitMin upperLimitMax))

Generate a random N in a range.

N.randomIn ( n1, n10 )
--: Random.Generator
--:     (N
--:         (In
--:             (Up minX To (Add1 minX))
--:             (Up maxX To (Add10 maxX))
--:         )
--:     )
N (In (Fixed min_) max)
-> Emptiable (Stacked (N (In (Up x0 To x0) max))) Never

Ns increasing from 0 to n In the end, there are n numbers.

import Stack


N.until n6
    |> Stack.map (\_ -> N.add n3)
    --: Emptiable
    --:     (Stacked
    --:         (N
    --:             (In
    --:                 (Up minX To (Add3 minX))
    --:                 (Up maxX To (Add9 maxX))
    --:             )
    --:         )
    --:     )
    --:     Never
    |> Stack.map (\_ -> N.toInt)
--> Stack.topDown 3 [ 4, 5, 6, 7, 8, 9 ]


N.until atLeast7 |> Stack.map (\_ -> N.minAdd n3)
--: Emptiable
--:     (Stacked (N (Min (Up x To (Add10 x)))))
--:     Never

typesafe-array even knows the length! Wanna try it?

specific numbers

If the package exposed every number 0 → 1000+, tools can become unusably slow.

So only 0 → 16 are exposed, while larger numbers have to be generated locally.

Current method: generate them into a module exposing (n500, N500, Add500, ...) + import as N exposing (n500, ...)

In the future, elm-generate will allow auto-generating via elm-review.

type n

⏭ skip to last

type alias N0 =
N0able Never Possibly

Type for the exact natural number 0

type alias N1 =
N0able (N0able Never Possibly) Never

Type for the exact natural number 1

type alias N2 =
N0able (N0able (N0able Never Possibly) Never) Never

Type for the exact natural number 2

type alias N3 =
(N0able (N0able (N0able Never Possibly) Never) Never)
Never

Type for the exact natural number 3

type alias N4 =
(N0able (N0able (N0able Never Possibly) Never) Never)
Never
)
Never

Type for the exact natural number 4

type alias N5 =
(N0able (N0able (N0able Never Possibly) Never) Never)
Never
)
Never
)
Never

Type for the exact natural number 5

type alias N6 =
(N0able (N0able (N0able Never Possibly) Never) Never)
Never
)
Never
)
Never
)
Never

Type for the exact natural number 6

type alias N7 =
(N0able (N0able (N0able Never Possibly) Never) Never)
Never
)
Never
)
Never
)
Never
)
Never

Type for the exact natural number 7

type alias N8 =
(N0able (N0able (N0able Never Possibly) Never) Never)
Never
)
Never
)
Never
)
Never
)
Never
)
Never

Type for the exact natural number 8

type alias N9 =
(N0able (N0able (N0able Never Possibly) Never) Never)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never

Type for the exact natural number 9

type alias N10 =
(N0able (N0able (N0able Never Possibly) Never) Never)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never

Type for the exact natural number 10

type alias N11 =
(N0able (N0able (N0able Never Possibly) Never) Never)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never

Type for the exact natural number 11

type alias N12 =
(N0able (N0able (N0able Never Possibly) Never) Never)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never

Type for the exact natural number 12

type alias N13 =
(N0able (N0able (N0able Never Possibly) Never) Never)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never

Type for the exact natural number 13

type alias N14 =
(N0able (N0able (N0able Never Possibly) Never) Never)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never

Type for the exact natural number 14

type alias N15 =
(N0able (N0able (N0able Never Possibly) Never) Never)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never

Type for the exact natural number 15

type alias N16 =
(N0able (N0able (N0able Never Possibly) Never) Never)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never

Type for the exact natural number 16

type N0able successor possiblyOrNever
= N0 possiblyOrNever
| Add1 successor

Base type of N0, Add1 n following allowable-state:

Is the parameter possiblyOrNever set to Never like in

type alias Add1 n =
    N0able n Never

N0 is impossible to construct.

Is the parameter possiblyOrNever set to Possibly like in

type alias N0 =
    N0able Never Possibly

N0 is a possible value.

type alias Add1 n =
N0able n Never

Type for the natural number 1 + some natural number n

type alias Add2 n =
N0able (N0able n Never) Never

Type for the natural number 2 + some natural number n

type alias Add3 n =
N0able (N0able (N0able n Never) Never) Never

Type for the natural number 3 + some natural number n

type alias Add4 n =
N0able (N0able (N0able (N0able n Never) Never) Never) Never

Type for the natural number 4 + some natural number n

type alias Add5 n =
(N0able (N0able (N0able (N0able n Never) Never) Never) Never)
Never

Type for the natural number 5 + some natural number n

type alias Add6 n =
(N0able (N0able (N0able (N0able n Never) Never) Never) Never)
Never
)
Never

Type for the natural number 6 + some natural number n

type alias Add7 n =
(N0able (N0able (N0able (N0able n Never) Never) Never) Never)
Never
)
Never
)
Never

Type for the natural number 7 + some natural number n

type alias Add8 n =
(N0able (N0able (N0able (N0able n Never) Never) Never) Never)
Never
)
Never
)
Never
)
Never

Type for the natural number 8 + some natural number n

type alias Add9 n =
(N0able (N0able (N0able (N0able n Never) Never) Never) Never)
Never
)
Never
)
Never
)
Never
)
Never

Type for the natural number 9 + some natural number n

type alias Add10 n =
(N0able (N0able (N0able (N0able n Never) Never) Never) Never)
Never
)
Never
)
Never
)
Never
)
Never
)
Never

Type for the natural number 10 + some natural number n

type alias Add11 n =
(N0able (N0able (N0able (N0able n Never) Never) Never) Never)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never

Type for the natural number 11 + some natural number n

type alias Add12 n =
(N0able (N0able (N0able (N0able n Never) Never) Never) Never)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never

Type for the natural number 12 + some natural number n

type alias Add13 n =
(N0able (N0able (N0able (N0able n Never) Never) Never) Never)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never

Type for the natural number 13 + some natural number n

type alias Add14 n =
(N0able (N0able (N0able (N0able n Never) Never) Never) Never)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never

Type for the natural number 14 + some natural number n

type alias Add15 n =
(N0able (N0able (N0able (N0able n Never) Never) Never) Never)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never

Type for the natural number 15 + some natural number n

type alias Add16 n =
(N0able (N0able (N0able (N0able n Never) Never) Never) Never)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never
)
Never

Type for the natural number 16 + some natural number n

n0 : N (In (Up minX To minX) (Up maxX To maxX))

The specific natural number 0

n1 : N (In (Up minX To (Add1 minX)) (Up maxX To (Add1 maxX)))

The specific natural number 1

n2 : N (In (Up minX To (Add2 minX)) (Up maxX To (Add2 maxX)))

The specific natural number 2

n3 : N (In (Up minX To (Add3 minX)) (Up maxX To (Add3 maxX)))

The specific natural number 3

n4 : N (In (Up minX To (Add4 minX)) (Up maxX To (Add4 maxX)))

The specific natural number 4

n5 : N (In (Up minX To (Add5 minX)) (Up maxX To (Add5 maxX)))

The specific natural number 5

n6 : N (In (Up minX To (Add6 minX)) (Up maxX To (Add6 maxX)))

The specific natural number 6

n7 : N (In (Up minX To (Add7 minX)) (Up maxX To (Add7 maxX)))

The specific natural number 7

n8 : N (In (Up minX To (Add8 minX)) (Up maxX To (Add8 maxX)))

The specific natural number 8

n9 : N (In (Up minX To (Add9 minX)) (Up maxX To (Add9 maxX)))

The specific natural number 9

n10 : N (In (Up minX To (Add10 minX)) (Up maxX To (Add10 maxX)))

The specific natural number 10

n11 : N (In (Up minX To (Add11 minX)) (Up maxX To (Add11 maxX)))

The specific natural number 11

n12 : N (In (Up minX To (Add12 minX)) (Up maxX To (Add12 maxX)))

The specific natural number 12

n13 : N (In (Up minX To (Add13 minX)) (Up maxX To (Add13 maxX)))

The specific natural number 13

n14 : N (In (Up minX To (Add14 minX)) (Up maxX To (Add14 maxX)))

The specific natural number 14

n15 : N (In (Up minX To (Add15 minX)) (Up maxX To (Add15 maxX)))

The specific natural number 15

n16 : N (In (Up minX To (Add16 minX)) (Up maxX To (Add16 maxX)))

The specific natural number 16

compare

Int clamp

intAtLeast : N (In min max_) -> Int -> N (Min min)

A N (Min ...) from an Int; if the Int < minimum, minimum is returned.

0
    |> N.intAtLeast n3
    --: N (Min (Up x To (Add3 x)))
    |> N.toInt
--> 3


9
    |> N.intAtLeast n3
    --: N (Min (Up x To (Add3 x)))
    |> N.toInt
--> 9

You can also use this as an escape hatch if you know an Int must be at least minimum. But avoid it if you can do better, like

goodLength =
    List.foldl
        (\_ -> N.minAdd n1 >> N.minDown n1)
        (n0 |> N.maxNo)

To handle the case < minimum yourself → intIsAtLeast

( N
(In
lowerLimitMin
(Up lowerLimitMaxToUpperLimitMin_ To upperLimitMin)
)
, N
(In
(Fixed upperLimitMin)
(Up upperLimitMaxX To upperLimitMaxPlusX)
)
)
-> Int
-> N (In lowerLimitMin (Up upperLimitMaxX To upperLimitMaxPlusX))

Create a N (In ...) by clamping an Int between a minimum & maximum.

  • if the Int < minimum, minimum is returned
  • if the Int > maximum, maximum is returned

If you want to handle the cases < minimum & > maximum explicitly, use intIsIn.

0
    |> N.intIn ( n3, n12 )
    --: N (In (Up minX To (Add3 minX)) (Up minX To (Add12 minX)))
    |> N.toInt
--> 3


99
    |> N.intIn ( n3, n12 )
    |> N.toInt
--> 12


9
    |> N.intIn ( n3, n12 )
    |> N.toInt
--> 9




toDigit : Char -> Maybe (N (In (Up minX To minX) (Up maxX To (Add9 maxX))))
toDigit char =
    ((char |> Char.toCode) - ('0' |> Char.toCode))
        |> N.intIsIn ( n0, n9 )
        |> Result.toMaybe

Int compare

intIsAtLeast : N (In min max_) -> Int -> Result Int (N (Min min))

If the Int ≥ a given minimum, return Ok with the N (Min minimum), else Err with the input Int.

4 |> N.intIsAtLeast n5
--: Result Int (N (Min (Up x To (Add5 x))))
--> Err 4


1234 |> N.intIsAtLeast n5 |> Result.map N.toInt
--> Ok 1234
( N
(In
lowerLimitMin
(Up lowerLimitMaxToUpperLimitMin_ To upperLimitMin)
)
, N
(In
(Fixed upperLimitMin)
(Up upperLimitMaxX To upperLimitMaxPlusX)
)
)
-> Int
-> Result
Int
(N (Min (Up upperLimitMaxX To (Add1 upperLimitMaxPlusX))))
)
(N
(In lowerLimitMin (Up upperLimitMaxX To upperLimitMaxPlusX))
)

Compared to a range from a lower to an upper bound, is the Int in range, BelowOrAbove?

inputIntJudge : Int -> Result String (N (In (Up minX To (Add1 minX)) (Up maxX To (Add10 maxX))))
inputIntJudge int =
    case int |> N.intIsIn ( n1, n10 ) of
        Ok inRange ->
            inRange |> Ok
        Err (N.Below _) ->
            Err "must be ≥ 1"
        Err (N.Above _) ->
            Err "must be ≤ 100"


0 |> inputIntJudge
--> Err "must be ≥ 1"
type BelowOrAbove below above
= Below below
| Above above

The error result of comparing Ns.

  • Above: greater
  • Below: less

Values exist for each condition.

clamp

atLeast : N (In minNew max) -> N (In min_ max) -> N (Min minNew)

Cap the N to >= a given new lower limit.

n15 |> N.atLeast n10 |> N.toInt
--> 15


n5AtLeast |> N.atLeast (n10 |> maxNo)
--: N (Min (Up x To (Add10 x)))

(The type doesn't forbid that the lower limit you're comparing against is below the current lower limit.)

in_ :
( N (In minNew (Up minNewMaxToMaxNewMin_ To maxNewMin))
, N (In (Fixed maxNewMin) maxNew)
)
-> N (In min_ max_)
-> N (In minNew maxNew)

Clamp the number to between both given limits.

between5And9 |> N.in_ ( n10, n10 )
--: N (In (Up x0 To (Add10 x0)) (Up x1 To (Add10 x1)))


between5And15 |> N.in_ ( n5, n10 )
--: N (In (Up minX To (Add5 minX)) (Up maxX To (Add10 maxX)))


atLeast5 |> N.in_ ( n5, n10 )
--: N (In (Up minX To (Add5 minX)) (Up maxX To (Add10 maxX)))

There shouldn't be an upper limit? → atLeast

(The type doesn't forbid that the limits you're comparing against are beyond the current limits.)

compare maximum constrained

is :
N
(In
(Up minX To (Add1 comparedAgainstMinPlusXMinus1))
(Up maxX To (Add1 comparedAgainstMaxPlusXMinus1))
)
-> N (In min max)
-> Result
(N (In min (Up maxX To comparedAgainstMaxPlusXMinus1)))
(N (In (Up minX To (Add2 comparedAgainstMinPlusXMinus1)) max))
)
(N
(In
(Up minX To (Add1 comparedAgainstMinPlusXMinus1))
(Up maxX To (Add1 comparedAgainstMaxPlusXMinus1))
)
)

Is the N equal to, BelowOrAbove a given number?

giveAPresent { age } =
    case age |> N.is n18 of
        Err (N.Below younger) ->
            toy { age = younger }


        Err (N.Above older) ->
            book { age = older }


        Ok _ ->
            bigPresent


toy : { age : N (In min_ (Up maxX To (Add17 maxX)) } -> Toy


book :
    { age : N (In (Fixed (Add19 minMinus19_)) max_) }
    -> Book

(The type doesn't forbid that the number you're comparing against is below the current minimum or above the current maximum. → Err or Ok values don't necessarily follow min <= max for N (In min max ...) Luckily that's not a problem, since the values won't be produced anyway.)

( N
(In
lowerLimitMin
(Up lowerLimitMaxX To (Add1 lowerLimitMaxPlusXMinus1))
)
, N (In (Up upperLimitMinX To upperLimitMinPlusX) upperLimitMax)
)
-> N (In min max)
-> Result
(N (In min (Up lowerLimitMaxX To lowerLimitMaxPlusXMinus1)))
(N (In (Up upperLimitMinX To (Add1 upperLimitMinPlusX)) max))
)
(N (In lowerLimitMin upperLimitMax))

Compared to a range from a lower to an upper bound, is the N in range or BelowOrAbove?

isIn3To10 : N (In min_ max_) -> Maybe (N (In (Up minX To (Add3 minX)) (Up maxX To (Add10 maxX))))
isIn3To10 =
    N.isIn ( n3, n10 )
        >> Result.toMaybe


n9 |> isIn3To10 |> Maybe.map N.toInt
--> Just 9


n12 |> isIn3To10
--> Nothing

(The type doesn't forbid that the limits you're comparing against are below the current minimum, above the current maximum or in the wrong order. → Err or Ok values don't necessarily follow min <= max for N (In min max ...) Luckily that's not a problem, since the values won't be produced anyway.)

Here's some example-cases:

  • hi < lo < min < max → Below | Above
  • hi < min < lo < max → Below | Above
  • hi < min < max < lo → Below | Above
  • lo < hi < min < max → Above
  • max < hi < min < lo → Below
  • min < hi < lo < max → Below | Above
  • lo < min < hi < max → Ok | Above
  • max < min < hi < lo → Below
  • max < lo < hi < min → Below | Ok | Above ← the default case
  • lo < min < max < hi → Ok
  • min < lo < max < hi → Ok | Below
  • min < max < lo < hi → Below

.

  • did we miss something? → issue
  • feel motivated to add the other cases? → PR
N
(In
lowerLimitMin
(Up lowerLimitMaxX To (Add1 lowerLimitMaxMinus1PlusX))
)
-> N (In min max)
-> Result
(N (In min (Up lowerLimitMaxX To lowerLimitMaxMinus1PlusX)))
(N (In lowerLimitMin max))

Is the N below than or at least as big as a given number?

vote :
    { age : N (In (Fixed (Add18 minMinus18_)) max_) }
    -> Vote


tryToVote { age } =
    case age |> N.isAtLeast n18 of
        Err _ ->
            --😓
            Nothing


        Ok oldEnough ->
            vote { age = oldEnough } |> Just


factorial : N (In min_ max_) -> N (Min (Up x To (Add1 x)))
factorial =
    factorialBody


factorialBody : N (In min_ max_) -> N (Min (Up x To (Add1 x)))
factorialBody x =
    case x |> N.isAtLeast n1 of
        Err _ ->
            n1 |> N.maxNo


        Ok atLeast1 ->
            factorial (atLeast1 |> N.minSub n1)
                |> N.mul atLeast1

(The type doesn't forbid that the lower limit you're comparing against is below the current minimum or above the current maximum. → Err or Ok values don't necessarily follow min <= max for N (In min max ...) Luckily that's not a problem, since the values won't be produced anyway.)

N (In (Up upperLimitMinX To upperLimitMinPlusX) upperLimitMax)
-> N (In min max)
-> Result
(N (In (Up upperLimitMinX To (Add1 upperLimitMinPlusX)) max))
(N (In min upperLimitMax))

Is the N at most (Ok) or greater than (Err) a given number?

goToBelow18Party : { age : N (In min_ (Up maxTo17 To N17)) } -> Snack


tryToGoToBelow18Party { age } =
    case age |> N.isAtMost n17 of
        Ok below18 ->
            goToBelow18Party { age = below18 } |> Just


        Err _ ->
            Nothing

(The type doesn't forbid that the upper limit you're comparing against is below the current minimum or above the current maximum. → Err or Ok values don't necessarily follow min <= max for N (In min max ...) Luckily that's not a problem, since the values won't be produced anyway.)

alter

add :
N
(In (Up minPlusX To sumMinPlusX) (Up maxPlusX To sumMaxPlusX))
-> N (In (Up minX To minPlusX) (Up maxX To maxPlusX))
-> N (In (Up minX To sumMinPlusX) (Up maxX To sumMaxPlusX))

Add a given specific N.

between70And100 |> N.add n7
--: N (In (Up minX To (Add77 minX)) (Up maxX To (Add107 maxX)))

One addend has an unconstrained maximum? → minAdd

N (In (Up minPlusX To sumMinPlusX) addedMax_)
-> N (In (Up x To minPlusX) max_)
-> N (Min (Up x To sumMinPlusX))

To the N without a known maximum-constraint, add a number that (only) has information on how to add the minima.

atLeast70 |> N.minAdd n7
--: N (Min (Up x To (Add77 x))

Use add if both maxima are known differences as well.

If the added minimum is Fixed, supply the min manually to re-enable adding both minimum types!

atLeast5 |> N.minAdd (min n2 atLeastFixed2)
--: N (Min (Up x To (Add7 x)))
sub :
N
(In
(Down maxPlusX To differenceMaxPlusX)
(Down minPlusX To differenceMinPlusX)
)
-> N (In (Up minX To minPlusX) (Up maxX To maxPlusX))
-> N
(In
(Up minX To differenceMinPlusX)
(Up maxX To differenceMaxPlusX)
)

From the N in a range subtract another N in a range.

n6 |> N.sub n5
--→ n1
--: N
--:     (In
--:         (Up x0 To (Add1 x0))
--:         (Up x1 To (Add1 x1))
--:     )

One of the terms has no maximum constraint? → minSub

N
(In
subtractedDifference0_
(Down minPlusX To differenceMinPlusX)
)
-> N (In (Up minX To minPlusX) max)
-> N (In (Up minX To differenceMinPlusX) max)

From an N with an unknown maximum constraint, subtract a specific number

atLeast7 |> N.minSub n2
--: N (Min (Up x To (Add5 x)))


atLeast6 |> N.minSub between0And5
--: N (Min (Up x To (Add1 x)))


between6And12 |> N.minSub between1And5
--: N (In (Up minX To (Add1 minX)) (Up maxX To (Add12 maxX)))

Use sub if you want to subtract an N in a range.

N (In (Fixed (Add1 exponentMinMinus1_)) exponentMax_)
-> N (In min max_)
-> N (Min min)

N Raised to a given power p ≥ 1 → x ^ p ≥ x

atLeast5 |> N.toPower n2
--: N (Min (Up x To (Add5 x)))


atLeast2 |> N.toPower n5
--: N (Min (Up x To (Add2 x)))
N
(In
(Fixed (Add1 divisorMinMinus1_))
(Up divX To (Add1 divisorMaxPlusXMinus1))
)
-> N (In min_ max_)
-> N (In (Up x0 To x0) (Up divX To divisorMaxPlusXMinus1))

The remainder after dividing by a N d ≥ 1. We know x % d ≤ d - 1

atMost7 |> N.remainderBy n3
--: N (In (Up minX To minX) (Up maxX To (Add2 maxX)))
mul :
N (In (Fixed (Add1 multiplicandMinMinus1_)) multiplicandMax_)
-> N (In min max_)
-> N (Min min)

Multiply by a given n ≥ 1. which means x * n ≥ x.

atLeast5 |> N.mul n2
--: N (Min (Up x To (Add5 x)))


atLeast2 |> N.mul n5
--: N (Min (Up x To (Add2 x)))
div :
N (In (Fixed (Add1 divisorMinMinus1_)) divisorMax_)
-> N (In min_ max)
-> N (In (Up minX To minX) max)

Divide (//) by an N d ≥ 1.

  • → / 0 is impossible
  • → x / d <= x

.

atMost7
    |> N.div n3
    --: N (In (Up minX To minX) (Up maxX To (Add7 maxX)))
    |> N.toInt

broaden

toInt : N range_ -> Int

Drop the range constraints to feed another library with its Int representation.

toFloat : N range_ -> Float

Drop the range constraints to feed another library with its Float representation.

type information

min :
N (In minNew (Up minNewMaxToMin_ To min))
-> N (In (Fixed min) max)
-> N (In minNew max)

Set the minimum lower.

[ atLeast3, atLeast4 ]

Elm complains:

But all the previous elements in the list are: N (Min N3)

[ atLeast3
, atLeast4 |> N.min n3
]
N (In maxIncreasedMin_ (Down minPlusX To minDecreasedPlusX))
-> N (In (Up x To minPlusX) max)
-> N (In (Up x To minDecreasedPlusX) max)

Have a specific minimum in mind? → min

Want to decrease the lower bound by a fixed amount? → minDown

maxNo : N (In min max_) -> N (Min min)

On N (In min max)'s type, drop max to get a N (Min min).

between3And10 |> N.maxNo
--: N (Min (Up x To (Add3 x)))

Use it to unify different types of number minimum constraints like

[ atLeast1, between1And10 ]

elm complains:

But all the previous elements in the list are: N (Min (Up x To (Add1 x)))

[ atLeast1
, between1And10 |> N.maxNo
]
max :
N (In (Fixed maxNewMin) maxNew)
-> N (In min (Up maxToMaxNewMin_ To maxNewMin))
-> N (In min maxNew)

Make it fit into functions with require a higher maximum.

You should type arguments and stored types as broad as possible.

onlyAtMost18 : N (In min_ (Up maxX To (Add18 maxX)) -> ...


onlyAtMost18 between3And8 -- works

But once you implement onlyAtMost18, you might use the n in onlyAtMost19:

onlyAtMost18 n =
    -- onlyAtMost19 n → error
    onlyAtMost19 (n |> N.max n18)
N (In maxIncreasedMin_ (Up maxPlusX To maxIncreasedPlusX))
-> N (In min (Up x To maxPlusX))
-> N (In min (Up x To maxIncreasedPlusX))

Have a specific maximum in mind? → max

Want to increase the upper bound by a fixed amount? → maxUp

miss operation x?

Anything that can't be expressed with the available operations? → issue/PR

fancy

Building extensions to this library – like typesafe-array or other structures

While the internally stored Int can't directly be guaranteed to be in bounds by elm, minimum, maximum as their representation as a difference must be built as actual values checked by the compiler. No shenanigans like runtime errors for impossible cases.

minimumAsDifference : N (In minimumAsDifference maximum_) -> minimumAsDifference

The smallest allowed number promised by the range type as its representation as a difference

maximumAsDifference : N (In min_ maximumAsDifference) -> maximumAsDifference

The greatest allowed number promised by the range type as its representation as a difference

fixed : n -> Fixed n

To the exact given number, create a fixed difference from 0

differenceUp : Up middle To high -> Up low To middle -> Up low To high

Chain the difference Up to a higher number.

differenceDown : Down high To middle -> Up low To high -> Up low To middle

Chain the difference Down to a lower number.

upDifference : Up low To high -> low -> high

To the number, add a specific other one by supplying a difference.

downDifference : Down high To low -> high -> low

To the number, subtract a specific other one by supplying a difference.