Natural numbers within a typed range.
somewhere within a minimum & maximum
↓ minimum ↓ maximum
⨯ [✓ ✓ ✓ ✓ ✓ ✓ ✓] ⨯ ⨯ ⨯...-- number between 3 and 5
N (In (Fixed (Add3 minX_)) (Up maxTo5_ N5))
percent : N (In min_ (Up maxTo100 To N100)) -> Percentmin ≤ 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
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)))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) elementRemember: ↑ 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,
Only stored / result types should use the type Min:
↓ minimum ↓ or →
⨯ [✓ ✓ ✓ ✓ ✓ ✓ ✓...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)))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_)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)) elementRemember: ↑ and other Min/Exactly/Fixed are result/stored types, not argument types.
Can't store functions? → MinValue
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,
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.
Flag "The number's upper limit is unknown" used in the definition of Min:
type alias Min min =
In minimum InfinityUp low To high: an exact number as the difference high - low.
Down high To low: an exact number as the difference high - low.
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 --> 16Really only use this if you want the absolute value.
badLength =
List.length >> N.absmaybe, 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 n0Generate a random N in a range.
N.randomIn ( n1, n10 )
--: Random.Generator
--: (N
--: (In
--: (Up minX To (Add1 minX))
--: (Up maxX To (Add10 maxX))
--: )
--: )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)))))
--: Nevertypesafe-array even knows the length!
Wanna try it?
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.
nType for the exact natural number 0
Type for the exact natural number 1
Type for the exact natural number 2
Type for the exact natural number 3
Type for the exact natural number 4
Type for the exact natural number 5
Type for the exact natural number 6
Type for the exact natural number 7
Type for the exact natural number 8
Type for the exact natural number 9
Type for the exact natural number 10
Type for the exact natural number 11
Type for the exact natural number 12
Type for the exact natural number 13
Type for the exact natural number 14
Type for the exact natural number 15
Type for the exact natural number 16
n +The natural number 1 + another given natural number n
The natural number 2 + another given natural number n
The natural number 3 + another given natural number n
The natural number 4 + another given natural number n
The natural number 5 + another given natural number n
The natural number 6 + another given natural number n
The natural number 7 + another given natural number n
The natural number 8 + another given natural number n
The natural number 9 + another given natural number n
The natural number 10 + another given natural number n
The natural number 11 + another given natural number n
The natural number 12 + another given natural number n
The natural number 13 + another given natural number n
The natural number 14 + another given natural number n
The natural number 15 + another given natural number n
The natural number 16 + another given natural number n
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
--> 15Don't know both maxima? → minAtLeast
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
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_
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)))minAtLeastatLeastatMost(The type doesn't forbid that the limits you're comparing against are beyond the current limits.)
Int clampA 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
--> 9You 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
Create a N (In ...) by clamping an Int between a minimum & maximum.
Int < minimum, minimum is returnedInt > maximum, maximum is returnedIf 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.toMaybeIs 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.)
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 | Abovehi < min < lo < max → Below | Abovehi < min < max < lo → Below | Abovelo < hi < min < max → Abovemax < hi < min < lo → Belowmin < hi < lo < max → Below | Abovelo < min < hi < max → Ok | Abovemax < min < hi < lo → Belowmax < lo < hi < min → Below | Ok | Above ← the default caselo < min < max < hi → Okmin < lo < max < hi → Ok | Belowmin < max < lo < hi → Below.
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.)
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.)
Int compareIf 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 1234Compared 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"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)))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)))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)))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)))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)))Divide (//) by an N d ≥ 1.
/ 0 is impossiblex / d <= x.
atMost7
|> N.div n3
--: N (In (Up minX To minX) (Up maxX To (Add7 maxX)))
|> N.toIntDrop the range constraints
to feed another library with its Int representation.
Drop the range constraints
to feed another library with its Float representation.
Drop the range constraints
to feed another library with its String representation.
The same as
toInt |> String.fromIntIn some cases, it's better or required not to store functions etc. in app state, events, ...:
== (accidentally) → crash, for exampleCalling == 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.
Number with Fixed range → number with equatable Value range
Number with equatable Value range → number with Fixed range to be altered, compared, ...
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
]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 -- worksBut once you implement onlyAtMost18, you might use the n in onlyAtMost19:
onlyAtMost18 n =
-- onlyAtMost19 n → error
onlyAtMost19 (n |> N.max n18)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
]Anything that can't be expressed with the available operations? → issue/PR
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.
Its limits.
Both its minimumAsDifference
and its maximumAsDifference
The smallest allowed number promised by the range type as its representation as a difference
The greatest allowed number promised by the range type as its representation as a difference
A difference represented as an Int >= 0
between3And6
|> N.maximumAsDifference
|> N.differenceToInt
--→ 6This enables for example specific
Create an N with the value = minimum = maximum of a given difference
N.exactly (between3And6 |> N.minimumAsDifference)
|> N.maximumAsDifference
|> N.differenceToInt
--→ 3See differenceToInt
Chain the difference Up to a higher number
Chain the difference Down to a lower number
To the number, add another number like shown in a given difference
From the number, subtract another number like shown in a given difference
Base type of N0, Add1 n following allowable-state:
Is the parameter possiblyOrNever set to Never like in
type alias Add1 n =
N0able n NeverN0 is impossible to construct.
Is the parameter possiblyOrNever set to Possibly like in
type alias N0 =
N0able Never PossiblyN0 is a possible value.
A bounded natural number
>= 0argument type
In (Add4 minMinus4_) N15says:4 + 0|4 + 1|4 + 2|...+some variablemaxTo15is15which means: the maximum is ≤ 15result type
[
Up low To high] is a representation as a difference of the limithigh - lowThis enables adding, subtracting. Consider it an implementation detail.
stored type
what to put in declared types like
ModelThey are like result types but type variables are set to
N0.more type examples at
In,Min