lue-bird/elm-bounded-nat/28.0.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_))

This 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 (Value N4))


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


-- = 3
N (ExactlyValue N3)

more type examples at In, Min

bounds

type In min max

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 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 (Up0 x_) ??)
                ↓
abs : 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? → MinValue

type alias Fixed n =
Up N0 To n

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 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 =
InFixed n 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 (Up0 x_))

The absolute value of an Int which is always ≥ 0

-4
    |> N.abs
    --: N (Min (Up0 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.addMin n1 >> N.minDown 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.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 (Up1 minX_) (Up10 maxX_)))

ArraySized.upTo to create increasing Ns. ArraySized will even know the final length in its type! 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 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

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 (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.atLeast n10
    --: N (In (Up10 x_) (Fixed N15))
    |> N.toInt
--> 15

Don't know both maxima? → minAtLeast

N (In lowerLimitMin lowerLimitMax_)
-> N (In min_ max_)
-> N (Min lowerLimitMin)

Cap the N to >= a given new lower limit

n5AtLeast |> N.minAtLeast 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.minAtLeast n10 |> N.toInt
--: N (Min (Up10 x_))

Know both maxima? → atLeast

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

Cap the N to <= a given new upper limit

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

Clamp the number to between both given limits

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


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


atLeast5 |> N.in_ ( n5, n10 )
--: N (In (Up5 minX_) (Up10 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.)

Int clamp

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

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

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


9
    |> N.intAtLeast 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.minDown 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.intIn ( n3, n12 )
    --: N (In (Up3 minX_) (Up12 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 (Up0 minX_) (Up9 maxX_)))
toDigit char =
    ((char |> Char.toCode) - ('0' |> Char.toCode))
        |> N.intIsIn ( n0, n9 )
        |> Result.toMaybe

compare

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

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

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.intIn ( n0, n10 ) 3
    |> N.greater (N.intIn ( n0, n10 ) 1)
    |> N.toInt
--> 3


N.intIn ( n0, n10 ) 3
    |> N.greater (N.intIn ( 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 atLeast 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.intIn ( n0, n10 ) 3
    |> N.smaller (N.intIn ( n0, n10 ) 1)
    |> N.toInt
--> 1


N.intIn ( n0, n10 ) 3
    |> N.smaller (N.intIn ( 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 atMost instead!

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"

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 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.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 (In min_ max_)
-> N
(In (Up0 remainderMinX_) (Up divMaxX To divisorMaxPlusXMinus1))

The remainder after dividing by a 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

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

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

Number with Fixed minimum → number with equatable Value minimum

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

Number with equatable Value minimum → number with Fixed minimum

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

Number with Fixed maximum → number with equatable Value maximum

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

Number with equatable Value 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? → minDown

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? → maxUp

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

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

safe internals without functions

fixedToValue : Fixed n -> Value n

Fixed → equatable Value

fixedFromValue : Value n -> Fixed n

Fixed → equatable Value

inFixedToValue : InFixed min max -> InValue min max

InFixed → equatable InValue

inFixedFromValue : InValue min max -> InFixed min max

equatable InValue → InFixed