lue-bird/elm-bounded-nat/30.1.0

NOTE: the latest version is 35.0.0

N

Natural number within a typed range

type N range

A bounded natural number >= 0

result type

-- ≥ 4
N (Min (Up4 x_))


-- 2 ≤ n ≤ 12
N (In (Up2 minX_)) (Up12 maxX_))


-- n3 :
N (In (Up3 minX_) (Up3 maxX_))

The type variable enables adding, subtracting. Consider the "Up" thing an implementation detail

n3
    |> N.add n6
    --: N (In (Up9 minX_) (Up9 maxX_))
    |> N.toValue
--> n9 |> N.toValue

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_) (Up maxTo15_ To N15) says:

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

stored type

what to put in declared types like Model

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


-- 2 ≤ n ≤ 12
N (InFixed N2 N12)


-- = 3
N (Exactly N3)

There's also versions of this that don't contain functions internally:

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


-- 2 ≤ n ≤ 12
N (InFixedValue N2 N12)


-- = 3
N (ExactlyValue N3)

more type examples at In, Min

bounds

type In lowestPossibleAsDifference highestPossibleAsDifference

somewhere within a minimum & maximum

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

argument type in a range

-- 3 ≤ n ≤ 5
N (In (Fixed (Add3 minX_)) (Up maxTo5_ N5))


-- 0 ≤ n ≤ 100
percent : N (In min_ (Up maxTo100_ To N100)) -> Percent

For every constructable value, the minimum is smaller than the maximum

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 (Up3 minX_) (Up3 maxX_))


between3And6 : N (In (Up3 minX_) (Up6 maxX_))


between3And6 |> N.add n3
--: N (In (Up6 minX_) (Up9 maxX_))

stored type in a range

An example where this is useful using typesafe-array:

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


type alias TreeBinaryFull 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 FixedValue for other code that relies (or performs better) on structural ==
type alias Min lowestPossibleAsDifference =
In lowestPossibleAsDifference (Fixed Infinity)

Only stored / result types should use the type Min:

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

result type without maximum constraint

Sometimes, you simply cannot compute a maximum

intToAbsolute : Int -> N (In (Up0 x_) ??)
                ↓
intToAbsolute : Int -> N (Min (Up0 x_))


-- n ≥ 5
atLeast5 : N (Min (Up5 x_))


atLeast5 |> N.addMin n3
--: N (Min (Up8 x_))


n3 |> N.addMin atLeast5
--: N (Min (Up8 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

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 N1)) element

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

Can't store functions? → MinFixedValue

type alias Fixed asNumber =
Up N0 To asNumber

The difference from 0 to a given n

A stored type looks like a result type where every Up<n> x is instead Fixed N<n>

Do not use == on 2 numbers in a Fixed range. It will lead to elm crashing because differences are stored as functions internally. Instead,

  • compare in your code
  • convert to FixedValue for other code that relies (or performs better) on structural ==
type alias InFixed lowestPossibleAsNumber highestPossibleAsNumber =
(Fixed lowestPossibleAsNumber)
(Fixed highestPossibleAsNumber)

Lower and upper limits Fixed. For stored types only

Can't store functions? → InFixedValue

type alias Exactly asNumber =
InFixed asNumber asNumber

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

= Infinity

"The limit is unknown".

Used in the definition of Min:

type alias Min min =
    In minimum (Fixed Infinity)

and MinFixedValue

type alias MinFixedValue min =
    In (FixedValue min) (FixedValue Infinity)

which can be simplified to

type alias MinFixedValue min =
    InFixedValue min Infinity

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

type Up lowRepresentationAsNumber toTag highRepresentationAsNumber

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

( 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.inRandom ( n1, n10 )
--: Random.Generator
--:     (N (In (Up1 minX_) (Up10 maxX_)))
( N
(In
lowerLimitMin
(Up lowerLimitMaxToUpperLimitMin_ To upperLimitMin)
)
, N (In (Fixed upperLimitMin) upperLimitMax)
)
-> Fuzzer (N (In lowerLimitMin upperLimitMax))

Fuzzer for an N in a given range. For larger ranges, smaller Ns are preferred

import Fuzz


N.inFuzz ( n3, n6 )
    |> Fuzz.map N.toInt
    |> Fuzz.examples 10
--> [ 5, 6, 3, 3, 4, 6, 3, 6, 3, 5 ]
( N
(In
lowerLimitMin
(Up lowerLimitMaxToUpperLimitMin_ To upperLimitMin)
)
, N (In (Fixed upperLimitMin) upperLimitMax)
)
-> Fuzzer (N (In lowerLimitMin upperLimitMax))

Fuzzer for an N in a given range. In contrast to N.inFuzz, all Ns are equally as likely, even for larger ranges

import Fuzz


N.inFuzzUniform ( n2, n2 |> N.toPower n13 )
    |> Fuzz.map N.toInt
    |> Fuzz.examples 3
--> [ 2244, 4842, 3566 ]


N.inFuzz ( n2, n2 |> N.toPower n13 )
    |> Fuzz.map N.toInt
    |> Fuzz.examples 3
--> [ 26, 82, 17 ]

ArraySized.upTo to create increasing Ns. ArraySized will even know the final length in its type! Wanna try?

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 N.Local exposing (n<n>, N<n>, Add<n>, Up<n>, ...) + import N.Local as N exposing (n<n>, N<n>, Add<n>, Up<n>, ...)

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

type n

⏭ skip to last

type alias N0 =
N0OrAdd1 Possibly Never
type alias N1 =
N0OrAdd1 Never (N0OrAdd1 Possibly Never)
type alias N2 =
N0OrAdd1 Never (N0OrAdd1 Never (N0OrAdd1 Possibly Never))
type alias N3 =
Never
(N0OrAdd1 Never (N0OrAdd1 Never (N0OrAdd1 Possibly Never)))
type alias N4 =
Never
Never
(N0OrAdd1 Never (N0OrAdd1 Never (N0OrAdd1 Possibly Never)))
)
type alias N5 =
Never
Never
Never
(N0OrAdd1 Never (N0OrAdd1 Never (N0OrAdd1 Possibly Never)))
)
)
type alias N6 =
Never
Never
Never
Never
(N0OrAdd1 Never (N0OrAdd1 Never (N0OrAdd1 Possibly Never)))
)
)
)
type alias N7 =
Never
Never
Never
Never
Never
(N0OrAdd1 Never (N0OrAdd1 Never (N0OrAdd1 Possibly Never)))
)
)
)
)
type alias N8 =
Never
Never
Never
Never
Never
Never
(N0OrAdd1 Never (N0OrAdd1 Never (N0OrAdd1 Possibly Never)))
)
)
)
)
)
type alias N9 =
Never
Never
Never
Never
Never
Never
Never
(N0OrAdd1 Never (N0OrAdd1 Never (N0OrAdd1 Possibly Never)))
)
)
)
)
)
)
type alias N10 =
Never
Never
Never
Never
Never
Never
Never
Never
(N0OrAdd1 Never (N0OrAdd1 Never (N0OrAdd1 Possibly Never)))
)
)
)
)
)
)
)
type alias N11 =
Never
Never
Never
Never
Never
Never
Never
Never
Never
(N0OrAdd1 Never (N0OrAdd1 Never (N0OrAdd1 Possibly Never)))
)
)
)
)
)
)
)
)
type alias N12 =
Never
Never
Never
Never
Never
Never
Never
Never
Never
Never
(N0OrAdd1 Never (N0OrAdd1 Never (N0OrAdd1 Possibly Never)))
)
)
)
)
)
)
)
)
)
type alias N13 =
Never
Never
Never
Never
Never
Never
Never
Never
Never
Never
Never
(N0OrAdd1 Never (N0OrAdd1 Never (N0OrAdd1 Possibly Never)))
)
)
)
)
)
)
)
)
)
)
type alias N14 =
Never
Never
Never
Never
Never
Never
Never
Never
Never
Never
Never
Never
(N0OrAdd1 Never (N0OrAdd1 Never (N0OrAdd1 Possibly Never)))
)
)
)
)
)
)
)
)
)
)
)
type alias N15 =
Never
Never
Never
Never
Never
Never
Never
Never
Never
Never
Never
Never
Never
(N0OrAdd1 Never (N0OrAdd1 Never (N0OrAdd1 Possibly Never)))
)
)
)
)
)
)
)
)
)
)
)
)
type alias N16 =
Never
Never
Never
Never
Never
Never
Never
Never
Never
Never
Never
Never
Never
Never
(N0OrAdd1 Never (N0OrAdd1 Never (N0OrAdd1 Possibly Never)))
)
)
)
)
)
)
)
)
)
)
)
)
)
type alias Add1 n =
N0OrAdd1 Never n

The natural number 1 + a given n

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

The natural number 2 + a given n

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

The natural number 3 + a given n

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

The natural number 4 + a given n

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

The natural number 5 + a given n

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

The natural number 6 + a given n

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

The natural number 7 + a given n

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

The natural number 8 + a given n

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

The natural number 9 + a given n

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

The natural number 10 + a given n

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

The natural number 11 + a given n

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

The natural number 12 + a given n

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

The natural number 13 + a given n

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

The natural number 14 + a given n

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

The natural number 15 + a given n

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

The natural number 16 + a given n

type Up x To (n + x)

⏭ skip to last

type alias Up0 x =
Up x To x

0 as the difference Up x To x

type alias Up1 x =
Up x To (N0OrAdd1 Never x)

1 as the difference Up x To (N0OrAdd1 Possibly x)

type alias Up2 x =
Up x To (N0OrAdd1 Never (N0OrAdd1 Never x))

2 as the difference Up x To (Add2 x)

type alias Up3 x =
Up x To (N0OrAdd1 Never (N0OrAdd1 Never (N0OrAdd1 Never x)))

3 as the difference Up x To (Add3 x)

type alias Up4 x =
x
Never
(N0OrAdd1 Never (N0OrAdd1 Never (N0OrAdd1 Never x)))
)

4 as the difference Up x To (Add4 x)

type alias Up5 x =
x
Never
Never
(N0OrAdd1 Never (N0OrAdd1 Never (N0OrAdd1 Never x)))
)
)

5 as the difference Up x To (Add5 x)

type alias Up6 x =
x
Never
Never
Never
(N0OrAdd1 Never (N0OrAdd1 Never (N0OrAdd1 Never x)))
)
)
)

6 as the difference Up x To (Add6 x)

type alias Up7 x =
x
Never
Never
Never
Never
(N0OrAdd1 Never (N0OrAdd1 Never (N0OrAdd1 Never x)))
)
)
)
)

7 as the difference Up x To (Add7 x)

type alias Up8 x =
x
Never
Never
Never
Never
Never
(N0OrAdd1 Never (N0OrAdd1 Never (N0OrAdd1 Never x)))
)
)
)
)
)

8 as the difference Up x To (Add8 x)

type alias Up9 x =
x
Never
Never
Never
Never
Never
Never
(N0OrAdd1 Never (N0OrAdd1 Never (N0OrAdd1 Never x)))
)
)
)
)
)
)

9 as the difference Up x To (Add9 x)

type alias Up10 x =
x
Never
Never
Never
Never
Never
Never
Never
(N0OrAdd1 Never (N0OrAdd1 Never (N0OrAdd1 Never x)))
)
)
)
)
)
)
)

10 as the difference Up x To (Add10 x)

type alias Up11 x =
x
Never
Never
Never
Never
Never
Never
Never
Never
(N0OrAdd1 Never (N0OrAdd1 Never (N0OrAdd1 Never x)))
)
)
)
)
)
)
)
)

11 as the difference Up x To (Add11 x)

type alias Up12 x =
x
Never
Never
Never
Never
Never
Never
Never
Never
Never
(N0OrAdd1 Never (N0OrAdd1 Never (N0OrAdd1 Never x)))
)
)
)
)
)
)
)
)
)

12 as the difference Up x To (Add12 x)

type alias Up13 x =
x
Never
Never
Never
Never
Never
Never
Never
Never
Never
Never
(N0OrAdd1 Never (N0OrAdd1 Never (N0OrAdd1 Never x)))
)
)
)
)
)
)
)
)
)
)

13 as the difference Up x To (Add13 x)

type alias Up14 x =
x
Never
Never
Never
Never
Never
Never
Never
Never
Never
Never
Never
(N0OrAdd1 Never (N0OrAdd1 Never (N0OrAdd1 Never x)))
)
)
)
)
)
)
)
)
)
)
)

14 as the difference Up x To (Add14 x)

type alias Up15 x =
x
Never
Never
Never
Never
Never
Never
Never
Never
Never
Never
Never
Never
(N0OrAdd1 Never (N0OrAdd1 Never (N0OrAdd1 Never x)))
)
)
)
)
)
)
)
)
)
)
)
)

15 as the difference Up x To (Add15 x)

type alias Up16 x =
x
Never
Never
Never
Never
Never
Never
Never
Never
Never
Never
Never
Never
Never
(N0OrAdd1 Never (N0OrAdd1 Never (N0OrAdd1 Never x)))
)
)
)
)
)
)
)
)
)
)
)
)
)

16 as the difference Up x To (Add16 x)

n0 : N (In (Up0 minX_) (Up0 maxX_))

The specific natural number 0

n1 : N (In (Up1 minX_) (Up1 maxX_))

The specific natural number 1

n2 : N (In (Up2 minX_) (Up2 maxX_))

The N 2

n3 : N (In (Up3 minX_) (Up3 maxX_))

The N 3

n4 : N (In (Up4 minX_) (Up4 maxX_))

The N 4

n5 : N (In (Up5 minX_) (Up5 maxX_))

The N 5

n6 : N (In (Up6 minX_) (Up6 maxX_))

The N 6

n7 : N (In (Up7 minX_) (Up7 maxX_))

The N 7

n8 : N (In (Up8 minX_) (Up8 maxX_))

The N 8

n9 : N (In (Up9 minX_) (Up9 maxX_))

The N 9

n10 : N (In (Up10 minX_) (Up10 maxX_))

The N 10

n11 : N (In (Up11 minX_) (Up11 maxX_))

The N 11

n12 : N (In (Up12 minX_) (Up12 maxX_))

The N 12

n13 : N (In (Up13 minX_) (Up13 maxX_))

The N 13

n14 : N (In (Up14 minX_) (Up14 maxX_))

The N 14

n15 : N (In (Up15 minX_) (Up15 maxX_))

The N 15

n16 : N (In (Up16 minX_) (Up16 maxX_))

The N 16

Int

intToAbsolute : Int -> N (Min (Up0 x_))

The Int's distance from 0. This "absolute value" is always ≥ 0

-4
    |> N.intToAbsolute
    --: N (Min (Up0 x_))
    |> N.toInt
--> 4


16 |> N.intToAbsolute |> N.toInt
--> 16

Really only use this if you want the absolute value

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

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

    arrayLength =
        Array.length >> N.intToAtLeast n0
N
(In
(Fixed (Add1 divisorMinMinus1_))
(Up divMaxX To (Add1 divisorMaxPlusXMinus1))
)
-> Int
-> N
(In (Up0 remainderMinX_) (Up divMaxX To divisorMaxPlusXMinus1))

Perform modular arithmetic by an N d ≥ 1. We know x % d ≤ d - 1

7
    |> N.intModBy n3
    --: N (In (Up0 minX_) (Up2 maxX_))
    |> N.toInt
--> 1

Works in the typical mathematical way for a negative Int

-7
    |> N.intModBy n3
    --: N (In (Up0 minX_) (Up2 maxX_))
    |> N.toInt
--> 2

Already have an N? → remainderBy

intToAtLeast : N (In lowerLimitMin lowerLimitMax_) -> Int -> N (Min lowerLimitMin)

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

0
    |> N.intToAtLeast n3
    --: N (Min (Up3 x_))
    |> N.toInt
--> 3


9
    |> N.intToAtLeast n3
    --: N (Min (Up3 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.addMin n1 >> N.minSubtract n1)
        (n0 |> N.maxToInfinity)

To handle the case < minimum yourself → intIsAtLeast

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

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.intToIn ( n3, n12 )
    --: N (In (Up3 minX_) (Up12 minX_))
    |> N.toInt
--> 3


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


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




toDigit : Char -> Maybe (N (In (Up0 minX_) (Up9 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 (Up5 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 (Up1 minX_) (Up10 maxX_)))
inputIntJudge =
    N.intIsIn ( n1, n10 )
        >> Result.mapError
            (\outOfRange ->
                case outOfRange of
                    N.Below _ ->
                        "≤ 0"
                    N.Above _ ->
                        "≥ 11"
            )


0 |> inputIntJudge
--> Err "≤ 0"

compare

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 (Up1 x_))
factorial =
    factorialBody


factorialBody : N (In min_ max_) -> N (Min (Up1 x_))
factorialBody x =
    case x |> N.isAtLeast n1 of
        Err _ ->
            n1 |> N.maxToInfinity


        Ok atLeast1 ->
            factorial (atLeast1 |> N.subtractMin n1)
                |> N.multiplyBy 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.)

type BelowOrAbove below above
= Below below
| Above above

The error result of comparing Ns

  • Above: greater than what it's compared against
  • Below: less than what it's compared against

Values exist for each condition

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_ (Up17 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 (Up3 minX_) (Up10 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
greater : N range -> N range -> N range

The maximum of both given numbers in the same range

Even though you can just use directly

N.intToIn ( n0, n10 ) 3
    |> N.greater (N.intToIn ( n0, n10 ) 1)
    |> N.toInt
--> 3


N.intToIn ( n0, n10 ) 3
    |> N.greater (N.intToIn ( n0, n10 ) 4)
    |> N.toInt
--> 4

this is rather supposed to be used as a primitive to build a structure maximum function:

ArraySized.fold Up N.greater

For clamping, try toAtLeast instead!

smaller : N range -> N range -> N range

The minimum of both given numbers in the same range

Even though you can just use directly

N.intToIn ( n0, n10 ) 3
    |> N.smaller (N.intToIn ( n0, n10 ) 1)
    |> N.toInt
--> 1


N.intToIn ( n0, n10 ) 3
    |> N.smaller (N.intToIn ( n0, n10 ) 4)
    |> N.toInt
--> 3

this is rather supposed to be used as a primitive to build a structure maximum function:

ArraySized.fold Up N.smaller

For clamping, try toAtMost instead!

More advanced stuff in section type information ) allowable-state

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 (Up77 minX_) (Up107 maxX_))

One addend has an unconstrained maximum? → addMin

N (In (Up minPlusX To sumMinPlusX) addedMax_)
-> N (In (Up minX To minPlusX) max_)
-> N (Min (Up minX 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.addMin n7
--: N (Min (Up77 x_))


n7 |> N.addMin atLeast70
--: N (Min (Up77 x_))

Use add if both maxima are known differences as well

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

atLeast5 |> N.addMin (atLeastFixed2 |> N.min n2)
--: N (Min (Up7 x_))
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.subtract n5
    --: N (In (Fixed N1) (Up1 x_))
    |> N.toValue
--> n1 |> N.toValue

One of the Ns has no maximum constraint? → N.subtractMin

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.subtractMin n2
--: N (Min (Fixed N5))


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


between6And12 |> N.subtractMin between1And5
--: N (In (Fixed min_) (Up12 maxX_))

Use N.subtract 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 (Up5 x_))


atLeast2 |> N.toPower n5
--: N (Min (Up2 x_))
N
(In
(Fixed (Add1 divisorMinMinus1_))
(Up divMaxX To (Add1 divisorMaxPlusXMinus1))
)
-> N range_
-> N
(In (Up0 remainderMinX_) (Up divMaxX To divisorMaxPlusXMinus1))

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

atMost7 |> N.remainderBy n3
--: N (In (Up0 minX_) (Up2 maxX_))
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.multiplyBy n2
--: N (Min (Up5 x_))


atLeast2 |> N.multiplyBy n5
--: N (Min (Up2 x_))
N (In (Fixed (Add1 divisorMinMinus1_)) divisorMax_)
-> N (In min_ max)
-> N (In (Up0 minX_) max)

Divide (//) by an N d ≥ 1

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

.

atMost7
    |> N.divideBy n3
    --: N (In (Up0 minX_) (Up7 maxX_))
    |> N.toInt

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.toAtLeast n10
--: N (In (Up10 x_) (Fixed N12))

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

n15
    |> N.toAtLeast n10
    --: N (In (Up10 x_) (Fixed N15))
    |> N.toInt
--> 15

Don't know both maxima? → toAtLeastMin

N (In lowerLimitMin lowerLimitMax_)
-> N range_
-> N (Min lowerLimitMin)

Cap the N to >= a given new lower limit

n5AtLeast |> N.toAtLeastMin n10
--: N (Min (Up10 x_))

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

n15AtLeast |> N.toAtLeastMin n10 |> N.toInt
--: N (Min (Up10 x_))

Know both maxima? → toAtLeast

N (In min upperLimitMax)
-> N (In min max_)
-> N (In min upperLimitMax)

Cap the N to <= a given new upper limit

atLeast3
    |> N.toAtMost (between4And5 |> N.min n3)
--: N (In (Up3 minX_) (Up5 maxX_))
  • To enforce a new minimum, too → toIn
( N
(In lowerLimitMin (Up minNewMaxToMaxNewMin_ To upperLimitMin))
, N (In (Fixed upperLimitMin) upperLimitMax)
)
-> N range_
-> N (In lowerLimitMin upperLimitMax)

Clamp the number to between both given limits

between5And9 |> N.toIn ( n10, n10 )
--: N (In (Up10 minX_) (Up10 maxX_))


between5And15 |> N.toIn ( n5, n10 )
--: N (In (Up5 minX_) (Up10 maxX_))


atLeast5 |> N.toIn ( n5, n10 )
--: N (In (Up5 minX_) (Up10 maxX_))

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

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

Equivalent to

toInt |> toFloat
toString : N range_ -> String

Drop the range constraints to feed another library with its String representation

Equivalent to

toInt |> String.fromInt

without internal functions

type FixedValue representedNumber

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 ==, for example
    • on hot module reloading, old code might end up remaining in the model
    • lamdera → doesn't work
    • accidental == call → crash

Calling == on a FixedValue 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 InFixedValue min max =
In (FixedValue min) (FixedValue max)

Lower and upper limits as FixedValues. For stored types only

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

type alias ExactlyValue n =
InFixedValue n n

For storing in a type. Allow only a specific FixedValue

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

type alias MinFixedValue min =
InFixedValue min Infinity

A lower limit as a FixedValue. For stored types only

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

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

Number with Fixed range → number with equatable FixedValue range

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

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

minToValue : N (In (Fixed min) max) -> N (In (FixedValue min) max)

Number with Fixed minimum → number with equatable FixedValue minimum

minFromValue : N (In (FixedValue min) max) -> N (In (Fixed min) max)

Number with equatable FixedValue minimum → number with Fixed minimum

maxToValue : N (In min (Fixed max)) -> N (In min (FixedValue max))

Number with Fixed maximum → number with equatable FixedValue maximum

maxFromValue : N (In min (FixedValue max)) -> N (In min (Fixed max))

Number with equatable FixedValue maximum → number with Fixed maximum

type information

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.minTo 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? → N.minTo

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

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 maxTo18_ To N18)) -> ...


onlyAtMost18 between3And8 -- works

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

onlyAtMost18 n =
    -- onlyAtMost19 n → error
    onlyAtMost19 (n |> N.maxTo n18)
maxToInfinity : N (In min max_) -> N (Min min)

On N (In min max)'s type, allow max to go up to infinity to get a N (Min min)

between3And10 |> N.maxToInfinity
--: N (Min (Up3 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 (Up1 x_))

[ atLeast1
, between1And10 |> N.maxToInfinity
]
N (In increaseMin_ (Up maxPlusX To maxIncreasedPlusX))
-> N (In min (Up maxX To maxPlusX))
-> N (In min (Up maxX To maxIncreasedPlusX))

Have a specific maximum in mind? → N.maxTo

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

allowable-state

Consider this an advanced technique for packages that use allowable-state. Any questions @lue in slack!

N (In (Fixed (N0OrAdd1 n0PossiblyOrNever minMinus1)) max)
-> Result n0PossiblyOrNever (N (In (Fixed (Add1 minMinus1)) max))

Transfer the knowledge about whether n0 is a possible value

trySubtracting1 :
    N (In (Fixed (N0OrAdd1 n0PossiblyOrNever minMinus1)) max)
    -> Emptiable (N (In (Fixed minMinus1) max)) possiblyOrNever
trySubtracting1 =
    \n ->
        case n |> N.isAtLeast1 of
            Err possiblyOrNever ->
                Emptiable.Empty possiblyOrNever


            Ok atLeast1 ->
                atLeast1 |> N.sub n1


stackRepeat :
    element
    -> N (In (Fixed (N0OrAdd1 possiblyOrNever minMinus1_)) max_)
    -> Emptiable (Stacked element) possiblyOrNever
stackRepeat toRepeat length =
    case length |> N.isAtLeast1 of
        Err possiblyOrNever ->
            Emptiable.Empty possiblyOrNever


        Ok lengthAtLeast1 ->
            Stack.topBelow
                toRepeat
                (List.repeat
                    (lengthAtLeast1 |> N.sub n1 |> N.toInt)
                    toRepeat
                )


stackLength :
    Emptiable (Stacked element) possiblyOrNever
    -> N (Min (Fixed (N0OrAdd1 possiblyOrNever N0)))
stackLength =
    \stack ->
        case stack of
            Emptiable.Empty possiblyOrNever ->
                -- adapt the variable minimum
                n0 |> N.min0Adapt (\_ -> possiblyOrNever) |> N.maxToInfinity


            Emptiable.Filled (TopBelow ( _, belowTop )) ->
                belowTop
                    |> List.length
                    |> N.intToAtLeast n0
                    |> N.add n1
                    -- downgrade the minimum
                    |> N.min0Adapt never

using N.min0Adapt

Stack and Emptiable are part of emptiness-typed

Cool, right?

(possiblyOrNever -> adaptedPossiblyOrNever)
-> N (In (Fixed (N0OrAdd1 possiblyOrNever minMinus1)) max)
-> N (In (Fixed (N0OrAdd1 adaptedPossiblyOrNever minMinus1)) max)

Change the possiblyOrNever type for the case that its min is 0

never allows you to adapt any variable, \_ -> yourVariablePossiblyOrNever swaps it for your given variable

stackLength :
    Emptiable (Stacked element) possiblyOrNever
    -> N (Min (Fixed (N0OrAdd1 possiblyOrNever N0)))
stackLength =
    \stack ->
        case stack of
            Emptiable.Empty possiblyOrNever ->
                -- adapt the variable minimum
                n0 |> N.min0Adapt (\_ -> possiblyOrNever) |> N.maxToInfinity


            Emptiable.Filled (TopBelow ( _, belowTop )) ->
                belowTop
                    |> List.length
                    |> N.intToAtLeast n0
                    |> N.add n1
                    -- downgrade the minimum
                    |> N.min0Adapt never

using isAtLeast1.

Stack and Emptiable are part of emptiness-typed

with (\_ -> Possible) it's just a worse version of minSubtract that might be useful in ultra rare situations

minSubtract1IfPossible :
    N (In (Fixed (N0OrAdd1 possiblyOrNever minMinus1)) max)
    -> N (In (Fixed (N0OrAdd1 Possibly minMinus1)) max)
minSubtract1IfPossible =
    N.min0Adapt (\_ -> Possibly)

miss an operation?

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

safe internals

Internal parts you can safely access and transform

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

Having those exposed can be useful when building extensions to this library like

range : N range -> range

Its limits containing both its min and its max

min : N (In min maximum_) -> min

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

max : N (In min_ max) -> max

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

Fixed difference up to Infinity

(N.fixedInfinity |> N.differenceToInt)
    == ((1 / 0) |> round)
--> True


(N.fixedInfinity |> N.fixedToNumber)
    == N.Infinity
--> True
fixedToNumber : Fixed representedNumber -> representedNumber

The N0OrAdd1 represented by this Fixed difference

import Possibly exposing (Possibly(..))


N.intToIn ( n3, n10 ) 5
    |> N.min
    |> N.fixedToNumber
--> N.Add1 (N.Add1 (N.Add1 (N.N0 Possible)))

useful

  • to preserve emptiness knowledge

    numberSuccessor :
        N0OrAdd1 possiblyOrNever successorMinus1
        -> Emptiable (Stacked element) possiblyOrNever
    numberSuccessor =
        \number ->
            case number of
                N.N0 possiblyOrNever ->
                    Emptiable.Empty possiblyOrNever
    
    
                N.Add1 successor ->
                    successor |> Emptiable.filled
  • as a tag

    type Element index
        = Element index
    
    
    element :
        N (Exactly index)
        -> Mapping (List element) (Element index) (Maybe element)
    element index =
        Typed.tag
            (Element (index |> N.min |> N.fixedToNumber))
            List.Extra.getAt

Can be altered with addDifference, subtractDifference.

To preserve the ability to turn the number into an Int, use fixedToValue

exactly : 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.intToIn ( n3, n6 ) 5
    |> N.min
    |> N.exactly
    |> N.max
    |> N.differenceToInt
--> 3

See differenceToInt

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

Chain the difference Up to a higher natural number

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

Chain the difference Down to a lower natural number

differenceToInt : Up low_ To high_ -> Int

A difference represented as an Int >= 0

N.intToIn ( n3, n6 ) 5
    |> N.max
    |> N.differenceToInt
--> 6

This enables for example exactly

addDifference : Up low To high -> low -> high

Increase a natural number, like shown in a given difference

subtractDifference : Down high To low -> high -> low

Decrease the natural number, like shown in a given difference

type N0OrAdd1 n0PossiblyOrNever successorMinus1
= N0 n0PossiblyOrNever
| Add1 successorMinus1

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

  • Is the parameter possiblyOrNever set to Never like in

    type alias Add1 n =
        N0aOrAdd1 Never n

    N0 is impossible to construct

  • Is the parameter possiblyOrNever set to Possibly like in

    type alias N0 =
        N0OrAdd1 Possibly Never

    N0 is a possible value

(n0PossiblyOrNever -> adaptedN0PossiblyOrNever)
-> N0OrAdd1 n0PossiblyOrNever minus1
-> N0OrAdd1 adaptedN0PossiblyOrNever minus1

Change the possiblyOrNever type for the case that the N0OrAdd1 is N0

(possiblyOrNever -> adaptedPossiblyOrNever)
-> Fixed (N0OrAdd1 possiblyOrNever endMinus1)
-> Fixed (N0OrAdd1 adaptedPossiblyOrNever endMinus1)

Change the possiblyOrNever type for the case that its N0OrAdd1 representation is N0

safe internals without functions

fixedToValue : Fixed n -> FixedValue n

Fixed → equatable FixedValue

fixedFromValue : FixedValue n -> Fixed n

Fixed → equatable FixedValue

inFixedToValue : InFixed min max -> InFixedValue min max

InFixed → equatable InFixedValue

inFixedFromValue : InFixedValue min max -> InFixed min max

equatable InFixedValue → InFixed