{"id":16572301,"url":"https://github.com/jonocarroll/nonempty","last_synced_at":"2025-09-04T07:36:50.725Z","repository":{"id":180338130,"uuid":"664974642","full_name":"jonocarroll/nonempty","owner":"jonocarroll","description":"Create and validate non-empty data structures","archived":false,"fork":false,"pushed_at":"2023-07-11T07:17:49.000Z","size":110,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-01-16T02:53:19.129Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"R","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jonocarroll.png","metadata":{"files":{"readme":"README.Rmd","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"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":"2023-07-11T06:58:56.000Z","updated_at":"2024-09-26T00:24:52.000Z","dependencies_parsed_at":null,"dependency_job_id":"1cfcb397-87a4-4e60-8df1-0fa86cebb3cc","html_url":"https://github.com/jonocarroll/nonempty","commit_stats":null,"previous_names":["jonocarroll/nonempty"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonocarroll%2Fnonempty","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonocarroll%2Fnonempty/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonocarroll%2Fnonempty/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonocarroll%2Fnonempty/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jonocarroll","download_url":"https://codeload.github.com/jonocarroll/nonempty/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":242040107,"owners_count":20061982,"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":[],"created_at":"2024-10-11T21:27:05.314Z","updated_at":"2025-03-05T14:20:49.286Z","avatar_url":"https://github.com/jonocarroll.png","language":"R","funding_links":[],"categories":["R"],"sub_categories":[],"readme":"---\noutput: github_document\neditor_options: \n  chunk_output_type: console\n---\n\n\u003c!-- README.md is generated from README.Rmd. Please edit that file --\u003e\n\n```{r, include = FALSE}\nknitr::opts_chunk$set(\n  collapse = TRUE,\n  comment = \"#\u003e\",\n  fig.path = \"man/figures/README-\",\n  out.width = \"100%\"\n)\n```\n\n# nonempty\n\n\u003c!-- badges: start --\u003e\n\u003c!-- badges: end --\u003e\n\nConstructs a strictly non-empty structure that cannot be made empty.\n\n## Installation\n\nYou can install the development version of {nonempty} like so:\n\n``` r\n# install.packages(\"remotes\")\nremotes::install_github(\"jonocarroll/nonempty\")\n```\n\n{nonempty} is dependency-free and relies entirely on S4 object validation.\n\n## Motivation\n\nThis blog post [https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/][parse] makes \nthe point that functions need to deal with edge-cases of inputs being `NULL`. Even \nin languages without an explicit `NULL` (e.g. Haskell has `Maybe`, Rust has `Option`) \nfunctions still need to deal with the possibility of `Nothing`, `None`, or whatever the missing \nkeyword is.\n\nThe example given there is a `head` function in Haskell, which is a strongly-typed language\nand so needs to handle the output being either a value, or `Nothing`, depending on whether \nthe input _has_ a first value, or is an empty list (`[]`)\n\n```haskell\nhead :: [a] -\u003e Maybe a\nhead (x:_) = Just x\nhead []    = Nothing\n```\n\nR, not being strongly-typed, doesn't have a type-safe return value, so it can return \neither a value, or `NULL`\n\n```{r}\nhead(c(3, 4, 5), 1)\nhead(NULL)\n```\n\nThe alternative propsed is to use a nonempty type consisting of a \"definitely present\" first \nvalue, along with a regular list (which may be empty)\n\n```haskell\ndata NonEmpty a = a :| [a]\n```\n\nin which case, the return type of `head` can be guaranteed\n\n```haskell\nhead :: NonEmpty a -\u003e a\nhead (x:|_) = x\n```\n\nThis `nonEmpty` type has the benefit that it can be safely assumed to be just that - not empty.\nPassing a `nonEmpty` structure to a function would not require testing if it is currently \n`NULL` - it is not.\n\nTo that end, {nonempty} provides such a structure in R which attempts to prevent \nbeing emptied.\n\n## Usage\n\nCreating a new `nonempty` structure is as simple as\n\n```{r}\nlibrary(nonempty)\na \u003c- nonempty(\"path/to/my/data\")\na\nvalidObject(a)\n```\n\nNote that you cannot create such as structure which is \"empty\" \n\n```{r, error = TRUE}\na \u003c- nonempty(\"\")\na \u003c- nonempty(c())\n```\n\nThe data can (in theory) be any structure, but only vectors have been properly implemented\n\n```{r}\nb \u003c- nonempty(c(2, 4, 6))\nb\nvalidObject(b)\n```\n\n`nonempty` objects can be combined, and remain (testably) non-empty\n\n```{r}\nnewval \u003c- b + b\nnewval\nclass(newval)\nvalidObject(newval)\n\ncombined \u003c- c(newval, newval)\ncombined\nclass(combined)\nvalidObject(combined)\n```\n\nThe important aspect is that these `nonempty` objects cannot \nbe created empty, and cannot be made empty\n\n```{r, error = TRUE}\n# can't add NULL\nnonval \u003c- b + NULL\n\n# can't substr to empty string\nsubstr(a, 0, 0)\n```\n\nThe structure can be subset, provided it remains nonempty\n```{r}\nb[1]\na[1] \u003c- \"some/other/path\"\na\n```\n\nbut can't be subset or replaced if it becomes empty\n\n```{r, error = TRUE}\nb[NULL]\na[1] \u003c- \"\"\na[] \u003c- \"\"\n```\n\n## Non-empty Structures in Dispatched Functions\n\nFunctions can now safely assume that a `nonempty` value is non-empty. \n\nSay we had some critical function that takes a string `\"file\"`. We \nwould usually have to defensively code tests that this is non-empty.\nWith a `nonempty` class, we can rest assured that it is indeed non-empty.\n\n```{r}\ncritical_f \u003c- function(file, y, ...) {\n  UseMethod(\"critical_f\")\n}\n\ncritical_f.default \u003c- function(file, y, ...) {\n  message(\"writing file without checking that `file` is nonempty\")\n}\n\ncritical_f.nonempty \u003c- function(file, y = 42) {\n  # it is safe to assume that `file` is nonempty here\n  message(\"writing y = \", y, \" to file: \", file)\n  writeLines(as.character(y), con = file)\n}\n```\n\nThe default way to programatically use the function would be to \ngenerate some `f` containing the string, which could accidentally\nbe empty\n\n```{r}\nf \u003c- \"myfile.txt\"\ncritical_f(f) # could accidentally be empty\n\nf \u003c- \"\"\ncritical_f(f) # bad\n```\n\nAlternatively, a `nonempty` value is validated to be non-empty\n\n```{r}\nf \u003c- nonempty(\"myfile.txt\")\ncritical_f(f, y = 100) # won't be empty\n```\n\nAny accidental emptying of the structure is prevented as it is\nattempted, not as it is attempted to be used\n\n```{r, error = TRUE}\n# this immediately fails validation\nf1 \u003c- nonempty(\"\")\ncritical_f(f1) # f1 won't be empty\n\n# this fails at the critical place\nf2 \u003c- nonempty(\"myfile.txt\")\n# [ ... ]\nf2[1] \u003c- \"\"\ncritical_f(f2, y = 2) # f2 was never emptied\n```\n\n## Limitations\n\nThis should run the validation on any `Ops` (`+`, `-`, ...) operation, `[` subsetting \nor replacement, `c()`, or `substr()` call, but there are always other ways to \nmake a variable empty. \n\nCreate a [GitHub Issue][issue] if you find one and I'll try to support preventing it.\n\n_Q: Can't you just overwrite a variable with an empty string? i.e. `x \u003c- \"\"`_\n\n_A:_ Yes.\n\n![](man/figures/toad.jpg)\n\n```{r, include = FALSE}\ntmpf \u003c- \"myfile.txt\"\nif (file.exists(tmpf)) {\n  file.remove(tmpf)\n}\n```\n\n[parse]: https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/\n[issue]: https://github.com/jonocarroll/nonempty/issues/new/choose\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonocarroll%2Fnonempty","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjonocarroll%2Fnonempty","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonocarroll%2Fnonempty/lists"}