lue-bird/elm-bounded-nat/25.1.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 (InFixed N2 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 ( Fixed ...) / Exactly / InFixed aren't argument types.


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

instead,

  • compare in your code
  • convert to Value for other code that relies (or performs better) on structural ==
type alias Min lowestPossibleValue =
In lowestPossibleValue Infinity

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 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.

Can't store functions? → MinValue

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>

Do not use == on 2 numbers of a Fixed difference. It will lead to elm crashing because differences are stored as functions.

instead,

  • compare in your code
  • convert to Value for other code that relies (or performs better) on structural ==
type alias InFixed min max =
In (Fixed min) (Fixed max)

Lower and upper limits Fixed. For stored types only.

Can't store functions? → InValue

type alias Exactly n =
In (Fixed n) (Fixed n)

Allow only a specific Fixed 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 alias Infinity =
Fixed { infinity : () }

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

type alias Min min =
    In minimum Infinity
type Up low toTag high

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.

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 alias Add1 n =
N0able n Never

The natural number 1 + another given natural number n

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

The natural number 2 + another given natural number n

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

The natural number 3 + another given natural number n

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

The natural number 4 + another given natural number n

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

The natural number 5 + another given natural number n

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

The natural number 6 + another given natural number n

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

The natural number 7 + another given natural number n

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

The natural number 8 + another given natural number n

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

The natural number 9 + another given natural number n

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

The natural number 10 + another given natural number n

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

The natural number 11 + another given natural number n

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

The natural number 12 + another given 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

The natural number 13 + another given 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

The natural number 14 + another given 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

The natural number 15 + another given 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

The natural number 16 + another given 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

N (In minNew (Up minNewMaxToMax_ To max))
-> N (In min_ (Fixed max))
-> N (In minNew (Fixed max))

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

between5And12 |> N.atLeast n10
--: N (In (Up x To (Add10 x)) (Fixed N12))

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

n15
    |> N.atLeast n10
    --: N (In (Up x To (Add10 x)) (Fixed N15))
    |> N.toInt
--> 15

Don't know both maxima? → minAtLeast

minAtLeast : N (In minNew maxNew_) -> N (In min_ max_) -> N (Min minNew)

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

n5AtLeast |> N.minAtLeast n10
--: 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

n15AtLeast |> N.minAtLeast n10 |> N.toInt
--: N (Min (Up x To (Add10 x)))

Know both maxima? → atLeast

N (In (Fixed takenMin) takenMax)
-> N (In (Up minToTakenMin_ To takenMin) max_)
-> N (In (Fixed takenMin) takenMax)

Cap the N to <= a given new upper limit.

between3And10
    |> N.atMost between2And5
--: N (In (Fixed 2) (Up x To (Add5 x)))

To replace the Fixed minimum with a difference (for results etc.) → min

To enforce a new minimum, too? → in_

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? → minAtLeast
  • To keep the current maximum? → atLeast
  • To keep the current minimum? → atMost

(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 min To differenceMin)
)
-> N (In (Fixed min) (Up maxX To maxPlusX))
-> N (In (Fixed differenceMin) (Up maxX To differenceMaxPlusX))

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

n6 |> N.sub n5
--→ n1
--: N (In (Fixed N1) (Up x To (Add1 x)))

One of the terms has no maximum constraint? → minSub

N (In subtractedDifference0_ (Down min To differenceMin))
-> N (In (Fixed min) max)
-> N (In (Fixed differenceMin) max)

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

atLeast7 |> N.minSub n2
--: N (Min (Fixed N5))


atLeast6 |> N.minSub between0And5
--: N (Min (Fixed N1))


between6And12 |> N.minSub between1And5
--: N (In (Fixed min) (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.

without internal functions

type Value n

In some cases, it's better or required not to store functions etc. in app state, events, ...:

  • serializability, for example
    • json import/export on the debugger → doesn't work
  • calling == (accidentally) → crash, for example

Calling == on a Value will yield the correct result instead of crashing

Fixed is defined as a difference from 0, so this independent type needed to be created.

You can just use Fixed when you don't have disadvantages storing functions.

type alias InValue min max =
In (Value min) (Value max)

Lower and upper limits as Values. For stored types only.

You can just use InFixed when you don't have disadvantages storing functions. See Value

type alias ExactlyValue n =
InValue n n

For storing in a type. Allow only a specific Value.

You can just use Exactly when you don't have disadvantages storing functions. See Value

type alias MinValue min =
In (Value min) InfinityValue

A lower limit as a Value. For stored types only.

You can just use Min ( Fixed ...) when you don't have disadvantages storing functions. See Value

type alias InfinityValue =
Value { infinity : () }

Infinity as a Value. Used in

type alias MinValue min =
    In (Value min) InfinityValue

You can just use Infinity when you don't have disadvantages storing functions. See Value

toValue : N (InFixed min max) -> N (InValue min max)

Number with Fixed range → number with equatable Value range

fromValue : N (InValue min max) -> N (InFixed min max)

Number with equatable Value range → number with Fixed range to be altered, compared, ...

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

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

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
]

miss operation x?

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

fancy

Building extensions to this library like

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.

range : N range -> range

Its limits. Both its minimumAsDifference and its maximumAsDifference

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

differenceToInt : Up low_ To high_ -> Int

A difference represented as an Int >= 0

between3And6
    |> N.maximumAsDifference
    |> N.differenceToInt
--→ 6

This enables for example specific

specific : Up low To high -> N (In (Up low To high) (Up low To high))

Create an N with the value = minimum = maximum of a given difference

N.exactly (between3And6 |> N.minimumAsDifference)
    |> N.maximumAsDifference
    |> N.differenceToInt
--→ 3

See differenceToInt

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 another number like shown in a given difference

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

From the number, subtract another number like shown in a given difference

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.

fancy without internal functions

fixedToValue : Fixed n -> Value n

Fixed → equatable Value

inFixedToValue : InFixed min max -> InValue min max

InFixed → equatable InValue

inValueToFixed : InValue min max -> InFixed min max

equatable InValue → InFixed to be altered, ...

valueToFixed : Value n -> Fixed n

Fixed → equatable Value