{"id":17771324,"url":"https://github.com/lue-bird/elm-bounded-nat","last_synced_at":"2025-04-01T15:18:51.134Z","repository":{"id":43425925,"uuid":"354354768","full_name":"lue-bird/elm-bounded-nat","owner":"lue-bird","description":"natural number in a typed range","archived":false,"fork":false,"pushed_at":"2023-08-25T07:33:41.000Z","size":887,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-27T08:47:57.908Z","etag":null,"topics":["elm","nat","natural-number","type-level","type-safe"],"latest_commit_sha":null,"homepage":"https://package.elm-lang.org/packages/lue-bird/elm-bounded-nat/latest/","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lue-bird.png","metadata":{"files":{"readme":"README.md","changelog":"changes.md","contributing":"contributing.md","funding":null,"license":"LICENSE","code_of_conduct":"code_of_conduct.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-04-03T17:37:32.000Z","updated_at":"2023-11-07T23:06:48.000Z","dependencies_parsed_at":"2024-10-26T21:56:22.289Z","dependency_job_id":"ac621e38-556b-4a31-b303-2a5bcb64dbbf","html_url":"https://github.com/lue-bird/elm-bounded-nat","commit_stats":{"total_commits":378,"total_committers":3,"mean_commits":126.0,"dds":0.41005291005291,"last_synced_commit":"42e4d519baf4de7b828787031b08d201d6d02503"},"previous_names":[],"tags_count":56,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lue-bird%2Felm-bounded-nat","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lue-bird%2Felm-bounded-nat/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lue-bird%2Felm-bounded-nat/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lue-bird%2Felm-bounded-nat/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lue-bird","download_url":"https://codeload.github.com/lue-bird/elm-bounded-nat/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246660077,"owners_count":20813338,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["elm","nat","natural-number","type-level","type-safe"],"created_at":"2024-10-26T21:31:40.891Z","updated_at":"2025-04-01T15:18:51.108Z","avatar_url":"https://github.com/lue-bird.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"## bounded-nat\n\nNatural number ≥ 0 that has extra information about its range _at compile-time_\n\n## example: `toHexChar`\n\n```elm\ntoHexChar : Int -\u003e Char\n```\n\n  - the _type_ doesn't show that an `Int` between 0 \u0026 15 is expected\n  - _the one implementing_ `toHexChar` has to handle the cases where the argument isn't between 0 \u0026 15\n      - either by introducing `Maybe` which will be carried throughout your program\n      - or by providing silent default error values like `'?'` or even worse `'0'`\n  - the `Int` range promise of the argument is lost after the operation\n\nwith `bounded-nat`:\n```elm\ntoHexChar : N (In min_ (Up maxTo15_ To N15)) -\u003e Char\n```\n\n  - the _type_ tells us that a number between 0 \u0026 15 is wanted\n  - the _user_ proves that the number is actually between 0 \u0026 15\n\nThe argument type says: Give me an integer ≥ 0 [`N`](N#N) [`In`](N#In) range\n  - `≥ 0` → _any_ minimum allowed → `min_` type variable that's only used once\n  - `≤ 15` → if we [increase](N#Up) some number (`maxTo15_` type variable that's only used once) by the argument's maximum, we get [`N15`](N#N15)\n\nUsers can prove this by _explicitly_\n\n  - using specific values\n\n    ```elm\n    red = rgbPercent { r = n100, g = n0, b = n0 }  --👍\n    n7 |\u003e N.subtract n1 |\u003e N.divideBy n2 |\u003e toHexChar --→ '3'\n    ```\n\n  - handling the possibility that a number isn't in the expected range\n\n    ```elm\n    toPositive : Int -\u003e Maybe (N (Min (Up1 x_)))\n    toPositive =\n        N.intIsAtLeast n1 \u003e\u003e Result.toMaybe\n    ```\n\n  - clamping\n\n    ```elm\n    floatPercent float =\n        float * 100 |\u003e round |\u003e N.intToIn ( n0, n100 )\n    ```\n\n  - there are more ways, but you get the idea 🙂\n\n\u0026emsp;\n\n\n## example: `toDigit`\n\n```elm\ntoDigit : Char -\u003e Maybe Int\n```\n\nYou might be able to do anything with this `Int` value, but you lost useful information:\n\n  - can the result even be negative?\n  - can the result even have multiple digits?\n\n```elm\ntoDigit :\n    Char -\u003e Maybe (N (In (Up0 minX_) (Up9 maxX_)))\n```\n\nThe type of an [`N`](N#N) value will reflect how much you and the compiler know\n\n  - at least a minimum value? [`Min`](N#Min)\n  - between a minimum \u0026 maximum value? [`In`](N#In)\n\n\n\u0026emsp;\n\n\n## example: `factorial`\n\n```elm\nintFactorial : Int -\u003e Int\nintFactorial x =\n    case x of\n        0 -\u003e\n            1\n\n        non0 -\u003e\n            non0 * intFactorial (non0 - 1)\n```\n\nThis forms an infinite loop if we call `intFactorial -1`...\n\nLet's disallow negative numbers here (\u0026 more)!\n\n```elm\nimport N exposing (N, In, Min, Up1, n1, n4)\n\n         -- for every `n ≥ 0`, `n! ≥ 1`\nfactorial : N (In min_ max_) -\u003e N (Min (Up1 x_))\nfactorial =\n    factorialBody\n\nfactorialBody : N (In min_ max_) -\u003e N (Min (Up1 x_))\nfactorialBody x =\n    case x |\u003e N.isAtLeast n1 of\n        Err _ -\u003e\n            n1 |\u003e N.maxToInfinity\n        Ok xMin1 -\u003e\n            -- xMin1 : N (Min ..1..), so xMin1 - 1 ≥ 0\n            factorial (xMin1 |\u003e N.subtractMin n1)\n                |\u003e N.multiplyBy xMin1\n\nfactorial n4 |\u003e N.toInt --\u003e 24\n```\n\n- nobody can put a negative number in\n- we have an extra promise! every result is `≥ 1`\n\nSeparate `factorial` \u0026 `factorialBody` are needed because there's [no support for polymorphic recursion](https://github.com/elm/compiler/issues/2180) 😢\n\nWe can do even better!\n`!19` is already `\u003e` the maximum safe `Int` `2^53 - 1`\n\n```elm\nsafeFactorial : N (In min_ (Up maxTo18_ To N18)) -\u003e N (Min (Up1 x_))\nsafeFactorial =\n    factorial\n```\n\nNo extra work\n\n\n## tips\n\n  - keep _as much type information as possible_ and drop it only where you need to: \"Wrap early, unwrap late\"\n      - [`elm-radio`](https://elm-radio.com/episode/wrap-early-unwrap-late/)\n      - [`elm-patterns`](https://sporto.github.io/elm-patterns/basic/wrap-early.html)\n\n  - keep _argument types as broad as possible_\n    \n    like instead of\n    ```elm\n    charFromCode : N (Min min_) -\u003e Char\n    ```\n    which you should never do, allow maximum-constrained numbers to fit as well:\n    ```elm\n    charFromCode : N (In min_ max_) -\u003e Char\n    ```\n\n## ready? go!\n\n- 👀 **[`typesafe-array`][typesafe-array]** shows that\n    - `N (In ...)` is very useful as an index\n    - `In`, `Min`, `Exactly`, ... can also describe a length\n\n[typesafe-array]: https://package.elm-lang.org/packages/lue-bird/elm-typesafe-array/latest/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flue-bird%2Felm-bounded-nat","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flue-bird%2Felm-bounded-nat","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flue-bird%2Felm-bounded-nat/lists"}