Natural number within a typed range
somewhere within a minimum & maximum
↓ minimum ↓ maximum
⨯ [✓ ✓ ✓ ✓ ✓ ✓ ✓] ⨯ ⨯ ⨯...-- 3 ≤ n ≤ 5
N (In (Fixed (Add3 minX_)) (Up maxTo5_ N5))
-- 0 ≤ n ≤ 100
percent : N (In min_ (Up maxTo100_ To N100)) -> PercentFor 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
n3 : N (In (Up3 minX_) (Up3 maxX_))
between3And6 : N (In (Up3 minX_) (Up6 maxX_))
between3And6 |> N.add n3
--: N (In (Up6 minX_) (Up9 maxX_))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) 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 (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_))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_)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)) elementRemember: ↑ and other Min/Exactly/Fixed are result/stored types, not argument types
Can't store functions? → MinValue
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,
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 (Up0 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.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 n0Generate 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?
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
nThe natural number 0
The natural number 1
The natural number 2
The natural number 3
The natural number 4
The natural number 5
The natural number 6
The natural number 7
The natural number 8
The natural number 9
The natural number 10
The natural number 11
The natural number 12
The natural number 13
The natural number 14
The natural number 15
The natural number 16
n +The natural number 1 + a given n
The natural number 2 + a given n
The natural number 3 + a given n
The natural number 4 + a given n
The natural number 5 + a given n
The natural number 6 + a given n
The natural number 7 + a given n
The natural number 8 + a given n
The natural number 9 + a given n
The natural number 10 + a given n
The natural number 11 + a given n
The natural number 12 + a given n
The natural number 13 + a given n
The natural number 14 + a given n
The natural number 15 + a given n
The natural number 16 + a given n
Up x To (n + x)3 as the difference Up x To (Add3 x)
4 as the difference Up x To (Add4 x)
5 as the difference Up x To (Add5 x)
6 as the difference Up x To (Add6 x)
7 as the difference Up x To (Add7 x)
8 as the difference Up x To (Add8 x)
9 as the difference Up x To (Add9 x)
10 as the difference Up x To (Add10 x)
11 as the difference Up x To (Add11 x)
12 as the difference Up x To (Add12 x)
13 as the difference Up x To (Add13 x)
14 as the difference Up x To (Add14 x)
15 as the difference Up x To (Add15 x)
16 as the difference Up x To (Add16 x)
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
--> 15Don't know both maxima? → minAtLeast
Cap the N to <= a given new upper limit
between3And10
|> N.atMost between2And5
--: N (In (Fixed 2) (Up5 x_))To replace the Fixed minimum with a difference
(for results etc.) → N.minTo
To enforce a new minimum, too? → in_
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_))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 (Up3 x_))
|> N.toInt
--> 3
9
|> N.intAtLeast n3
--: N (Min (Up3 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.addMin n1 >> N.minDown n1)
(n0 |> N.maxToInfinity)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 (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.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_ (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.)
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 | 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.
The error result of comparing Ns
Above: greater than what it's compared againstBelow: less than what it's compared againstValues exist for each condition
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.)
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.)
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
--> 4this is rather supposed to be used as a primitive to build a structure maximum function:
ArraySized.fold Up N.greaterFor clamping, try atLeast instead!
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
--> 3this is rather supposed to be used as a primitive to build a structure maximum function:
ArraySized.fold Up N.smallerFor clamping, try atMost instead!
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 (Up5 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 (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"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_))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.toValueOne of the Ns has no maximum constraint? → N.subtractMin
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 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_))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_))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_))Divide (//) by an N d ≥ 1
/ 0 is impossiblex / d <= x.
atMost7
|> N.divideBy n3
--: N (In (Up0 minX_) (Up7 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
Equivalent to
toInt |> toFloatDrop the range constraints
to feed another library with its String representation
Equivalent to
toInt |> String.fromIntIn some cases, it's better or required not to store functions etc. in app state, events, ...:
==, for example== call → crashCalling == 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.minTo 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 maxTo18_ To N18)) -> ...
onlyAtMost18 between3And8 -- worksBut once you implement onlyAtMost18, you might use the n in onlyAtMost19:
onlyAtMost18 n =
-- onlyAtMost19 n → error
onlyAtMost19 (n |> N.maxTo n18)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
]Anything that can't be expressed with the available operations? → issue/PR
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
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
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
--> 3See differenceToInt
Chain the difference Up to a higher natural number
Chain the difference Down to a lower natural number
A difference represented as an Int >= 0
N.intIn ( n3, n6 ) 5
|> N.max
|> N.differenceToInt
--> 6This enables for example exactly
Increase a natural number, like shown in a given difference
Decrease the natural 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 =
N0aOrAdd1 Never nN0 is impossible to construct
Is the parameter possiblyOrNever set to Possibly like in
type alias N0 =
N0OrAdd1 Possibly NeverN0 is a possible value
A bounded natural number
>= 0result type
This enables adding, subtracting. Consider the "Up" thing an implementation detail
argument type
In (Add4 minMinus4_) (Up maxTo15_ To N15)says:4+ 0|4+ 1|4+ 2|... which means it's ≥ 4+some variablemaxTo15is15which means it's ≤ 15stored type
what to put in declared types like
ModelThere's also versions of this that don't contain functions internally:
more type examples at
In,Min