Natural number within a typed range
somewhere within a minimum & maximum
↓ minimum ↓ maximum
⨯ [✓ ✓ ✓ ✓ ✓ ✓ ✓] ⨯ ⨯ ⨯...-- 3 ≤ n ≤ 5
N (In (On (Add3 minFrom3_)) (Up maxTo5_ To 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 (On (Add5 minFrom_)) 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 (On N2)) elementRemember: ↑ and other ... (On...) / InOn
are result/stored types, not argument types
Do not use == on 2 values storing a range.
It will lead to elm crashing because differences are stored as functions.
Instead,
Allow only a specific given represented number
Useful as a stored & argument type
in combination with typesafe-array,
not with Ns
byte : ArraySized (Exactly (On N8)) Bit -> Byte→ A given ArraySized must have exactly 8 Bits
type alias TicTacToeBoard =
ArraySized
(Exactly (On N3))
(ArraySized (Exactly (On N3)) TicTacToField)→ A given ArraySized must have exactly 3 by 3 TicTacToeFields
Use Exactly ( OnValue ...)
for storing in a type that can't handle internal functions
Only stored / result types should use Min
↓ minimum ↓ or →
⨯ [✓ ✓ ✓ ✓ ✓ ✓ ✓...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_))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 (On (Add5 minFrom5_)) 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 (On N1)) elementRemember: ↑ and other ... (On...) / InOn
are result/stored types, not argument types
You can just use Min ( On ...) when you don't have disadvantages storing functions.
If you can't store functions, OnValue
"The limit is unknown".
Used in the definition of Min
type alias Min min =
In minimum (OnValue Infinity)The fixed difference from 0 to a given number n
You'll find this either to require a certain minimum
entryOnlyForKids : N (In (On (Add6 minFrom6_)) (Up maxTo14_ To N14)) -> ()
entryOnlyForKids _ =
()Or in a stored type, which looks like a result type
where every Up<n> x is instead On N<n>
Do not use == on 2 numbers in a On range.
It will lead to elm crashing because differences are stored as functions internally.
Instead,
Up low To high: a specific number represented as the difference high - low
Down high To low: a specific number represented as the difference high - low
Generate a random N in a range
N.inRandom ( n1, n10 )
--: Random.Generator
--: (N (In (Up1 minX_) (Up10 maxX_)))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?
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)
IntThe 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
--> 16Really only use this if you want the absolute value
badLength =
List.length >> N.intToAbsolutemaybe, 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 n0Perform 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
--> 1Works in the typical mathematical way for a negative Int
-7
|> N.intModBy n3
--: N (In (Up0 minX_) (Up2 maxX_))
|> N.toInt
--> 2Already have an N? → remainderBy
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
--> 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.minSubtract 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.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.toMaybeInt 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 On, supply the N.minTo manually
to re-enable adding both minimum types!
atLeast5 |> N.addMin (atLeastOn2 |> 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 (On 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 (On N5))
atLeast6 |> N.subtractMin between0And5
--: N (Min (On N1))
between6And12 |> N.subtractMin between1And5
--: N (In (On 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 an 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.toIntCap the N to >= a given new lower limit
between5And12 |> N.toAtLeast n10
--: N (In (Up10 x_) (On 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_) (On N15))
|> N.toInt
--> 15Don't know both maxima? → toAtLeastMin
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_))toAtLeastMintoAtLeasttoAtMost(The type doesn't forbid that the limits you're comparing against are beyond the current limits)
Is the N below than or at least as big as a given number?
vote :
{ age : N (In (On (Add18 minFrom18_)) 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 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 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 (On (Add19 minFrom9_)) 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 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
--> 4this is rather supposed to be used as a primitive to build a structure maximum function:
ArraySized.fold Up N.greaterFor clamping, try toAtLeast instead!
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
--> 3this is rather supposed to be used as a primitive to build a structure maximum function:
ArraySized.fold Up N.smallerFor clamping, try toAtMost instead!
More advanced stuff in section type information ) allowable-state
Drop 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 OnValue will yield the correct result instead of crashing
On is defined as a difference from 0, so this independent type needed to be created
You can just use On when you don't have disadvantages storing functions
Number with On range → number with equatable OnValue range
Number with equatable OnValue range
→ number with On range to be altered, compared, ...
Set its minimum lower
[ atLeast3, atLeast4 ]Elm complains:
But all the previous elements in the list are:
N (Min N3)
[ atLeast3
, atLeast4 |> N.minTo n3
]To decrease its minimum by a relative amount, minSubtract
Decrease its minimum by a given relative amount.
To set its minimum to a specific absolute value, minTo
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)To increase its maximum by a relative amount, maxAdd
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
]Increase its maximum by a given relative amount.
To set its maximum to a specific absolute value, N.maxTo
Consider this an advanced technique for packages that use
allowable-state.
Any questions @lue in slack!
Transfer the knowledge about whether n0 is a possible value
stackRepeat :
element
-> N (In (On (N0OrAdd1 possiblyOrNever minFrom1_)) 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 (On (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 neverusing N.min0Adapt
Stack and Emptiable are part of emptiness-typed
Cool, right?
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 (On (N0OrAdd1 possiblyOrNever N0)))
stackLength =
\stack ->
case stack of
Emptiable.Empty possiblyOrNever ->
n0
-- adapt the variable minimum
|> N.min0Adapt (\_ -> possiblyOrNever)
-- allow a different min successor type
|> N.minAtLeast1Never
|> N.maxToInfinity
Emptiable.Filled (TopBelow ( _, belowTop )) ->
belowTop
|> List.length
|> N.intToAtLeast n0
|> N.add n1
-- downgrade the minimum
|> N.min0Adapt neverusing isAtLeast1, minAtLeast1Never.
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 (On (N0OrAdd1 possiblyOrNever minFrom1)) max)
-> N (In (On (N0OrAdd1 Possibly minFrom1)) max)
minSubtract1IfPossible =
N.min0Adapt (\_ -> Possibly)Change the successor type for the case that its min is 1 + ...
to allow adapting any variable
stackLength :
Emptiable (Stacked element) possiblyOrNever
-> N (Min (On (N0OrAdd1 possiblyOrNever N0)))
stackLength =
\stack ->
case stack of
Emptiable.Empty possiblyOrNever ->
n0
-- adapt the variable minimum
|> N.min0Adapt (\_ -> possiblyOrNever)
-- allow a different min successor type
|> N.minAtLeast1Never
|> N.maxToInfinity
Emptiable.Filled (TopBelow ( _, belowTop )) ->
belowTop
|> List.length
|> N.intToAtLeast n0
|> N.add n1
-- downgrade the minimum
|> N.min0Adapt neverusing isAtLeast1, min0Adapt.
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 and 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
(N.onInfinity |> N.differenceToInt)
== ((1 / 0) |> round)
--> True
(N.onInfinity |> N.onToNumber)
== N.Infinity
--> TrueThe N0OrAdd1 represented by this On difference
import Possibly exposing (Possibly(..))
N.intToIn ( n3, n10 ) 5
|> N.min
|> N.onToNumber
--> 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.filledas a tag
type Element index
= Element index
element :
N (Exactly (On index))
-> Mapping (List element) (Element index) (Maybe element)
element index =
Typed.tag
(Element (index |> N.min |> N.onToNumber))
List.Extra.getAtCan be altered with addDifference, subtractDifference.
To preserve the ability to turn the number into an Int, use onToValue
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
--> 3To extract an int directly, differenceToInt
This is a powerful thing since you can safely create numbers if you have a difference
and adapt its range afterwards with maxTo, minTo
or maxAdd, minSubtract
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.intToIn ( 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
Change the type
for the case that the N0OrAdd1 is Add1 minus1
Change the type
for the case that the N0OrAdd1 is Add1 minus1
A bounded natural number
>= 0result type
The type variable enables adding, subtracting. Consider the "
Up" thing an implementation detailargument type
In (On (Add4 minFrom4_)) (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