{"id":14066868,"url":"https://github.com/armcn/maybe","last_synced_at":"2025-04-12T00:34:24.350Z","repository":{"id":56933955,"uuid":"448698605","full_name":"armcn/maybe","owner":"armcn","description":"The Maybe Monad in R","archived":false,"fork":false,"pushed_at":"2023-08-10T00:01:38.000Z","size":1164,"stargazers_count":49,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-25T20:21:24.277Z","etag":null,"topics":["functional-programming","r","rstats"],"latest_commit_sha":null,"homepage":"https://armcn.github.io/maybe","language":"R","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/armcn.png","metadata":{"files":{"readme":"README.Rmd","changelog":"NEWS.md","contributing":null,"funding":null,"license":"LICENSE","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":"2022-01-16T23:27:32.000Z","updated_at":"2025-03-22T10:38:29.000Z","dependencies_parsed_at":"2024-08-13T07:11:42.470Z","dependency_job_id":"a08e039d-aaf3-4761-8586-ad8f1c82b76b","html_url":"https://github.com/armcn/maybe","commit_stats":{"total_commits":46,"total_committers":1,"mean_commits":46.0,"dds":0.0,"last_synced_commit":"1e921f6712225a6001b3a38b0abd6a0aa1bc222c"},"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/armcn%2Fmaybe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/armcn%2Fmaybe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/armcn%2Fmaybe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/armcn%2Fmaybe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/armcn","download_url":"https://codeload.github.com/armcn/maybe/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248501700,"owners_count":21114676,"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":["functional-programming","r","rstats"],"created_at":"2024-08-13T07:05:18.417Z","updated_at":"2025-04-12T00:34:24.328Z","avatar_url":"https://github.com/armcn.png","language":"R","funding_links":[],"categories":["R"],"sub_categories":[],"readme":"---\noutput: github_document\neditor_options: \n  markdown: \n    wrap: 72\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# maybe \u003cimg src=\"man/figures/hex.png\" align=\"right\" style=\"width: 25%;\"/\u003e\n\n\u003c!-- badges: start --\u003e\n\n[![CRAN\nstatus](https://www.r-pkg.org/badges/version/maybe)](https://CRAN.R-project.org/package=maybe)\n[![R-CMD-check](https://github.com/armcn/maybe/workflows/R-CMD-check/badge.svg)](https://github.com/armcn/maybe/actions)\n[![Codecov test\ncoverage](https://codecov.io/gh/armcn/maybe/branch/main/graph/badge.svg)](https://app.codecov.io/gh/armcn/maybe?branch=main)\n[![metacran downloads](https://cranlogs.r-pkg.org/badges/maybe)](https://cran.r-project.org/package=maybe)\n[![R-CMD-check](https://github.com/armcn/maybe/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/armcn/maybe/actions/workflows/R-CMD-check.yaml)\n\u003c!-- badges: end --\u003e\n\n# Overview\n\nThe maybe type represents the possibility of some value or nothing. It is often \nused instead of throwing an error or returning an undefined value like `NA` or \n`NULL`. The advantage of using a maybe type is that the functions which work with\nit are both composable and require the developer to explicitly acknowledge the \npotential absence of a value, helping to avoid unexpected behavior.\n\n## Installation\n\nYou can install the released version of maybe from [CRAN](https://CRAN.R-project.org) with:\n\n```{r, eval=FALSE}\ninstall.packages(\"maybe\")\n```\n\nAnd the development version from [GitHub](https://github.com/) with:\n\n``` r\n# install.packages(\"remotes\")\nremotes::install_github(\"armcn/maybe\")\n```\n\n# Usage\n\nThe following example shows how the maybe package can be used to create\na safe data processing pipeline. \n\n```{r}\nlibrary(maybe)\n\nsafe_filter \u003c- maybe(dplyr::filter, ensure = not_empty)\nsafe_mean \u003c- maybe(mean, ensure = not_undefined)\nsafe_pull \u003c- maybe(dplyr::pull)\n\nmean_mpg_of_cyl \u003c- function(.cyl) {\n  mtcars %\u003e% \n    safe_filter(cyl == .cyl) %\u003e% \n    and_then(safe_pull, mpg) %\u003e% \n    and_then(safe_mean) %\u003e% \n    with_default(0)\n}\n\nmean_mpg_of_cyl(8L)\nmean_mpg_of_cyl(100L)\n```\n\nHere is an example of working with data stored in JSON format.\n\n```{r}\nlibrary(purrr)\n\nparse_numbers \u003c- \n  function(x) filter_map(x, maybe(as.numeric))\n\nsafe_first \u003c- \n  maybe(function(x) x[[1]], ensure = not_empty)\n\nsum_first_numbers \u003c- function(json) {\n  jsonlite::fromJSON(json) %\u003e%\n    filter_map(compose(safe_first, parse_numbers)) %\u003e% \n    perhaps(reduce, default = 0)(`+`)\n}\n\nsum_first_numbers('{\"a\": [], \"b\": [1, 2.2, \"three\"], \"c\": [3]}')\nsum_first_numbers('{}')\nsum_first_numbers('1, 2, 3')\n```\n\n## The maybe type\n\nMaybe values can be used to model computations that may fail or have undefined \noutputs. For example, dividing by zero is mathematically undefined but in many \nprogramming languages, including R, infinity is returned. If it is not properly\naccounted for this may cause unexpected behavior later in the program. The maybe\ntype can be used to improve the safety of the divide function.\n\n```{r}\ndivide \u003c- function(a, b) {\n  a / b\n}\n\nsafe_divide \u003c- function(a, b) {\n  if (b == 0) nothing() else just(a / b)\n}\n\ndivide(10, 2)\nsafe_divide(10, 2)\n\ndivide(10, 0)\nsafe_divide(10, 0)\n```\n\n`safe_divide(10, 2)` returns `Just 5` and `safe_divide(10, 0)` \nreturns `Nothing`. These are the two possible values of the maybe type. It can \nbe `Just` the value, or it can be `Nothing`, the absence of a value. For the \nvalue to be used as an input to another function you need to specify what will \nhappen if the function returns `Nothing`. \n\nThis can be done using the `with_default` function. This function will return \nthe value contained in the `Just`, or if it is `Nothing` it will return the \ndefault. Think of a maybe value as a container. In this container can be `Just` \nthe value or `Nothing`. To use the contained value in a regular R function you \nneed to unwrap it first.\n\n```{r}\nsafe_divide(10, 2)\nsafe_divide(10, 2) %\u003e% with_default(0)\n\nsafe_divide(10, 0)\nsafe_divide(10, 0) %\u003e% with_default(0)\n```\n\n## Chaining maybe values\n\nThis may seem tedious to rewrite functions to return maybe values and then \nspecify a default value each time. This is where the maybe chaining functions\nbecome useful. \n\n`maybe_map` allows a regular R function to be evaluated on a maybe\nvalue. `maybe_map`, often called `fmap` in other languages, reaches into the \nmaybe value, applies a function to the value, then re-wraps the result in a \nmaybe. If the input is a `Just` value, the return value of `maybe_map` will also \nbe a `Just`. If it is `Nothing` the return value will be `Nothing`.\n\n```{r}\njust(9) %\u003e% maybe_map(sqrt)\nnothing() %\u003e% maybe_map(sqrt)\n```\n\nWhat if we wanted to chain multiple \"safe\" functions (functions that return \nmaybe values) together? The function `and_then`, often called `bind` in other \nlanguages, works similarly to `maybe_map` except the function provided must \nreturn a maybe value.\n\n```{r}\nsafe_max \u003c- function(a) {\n  if (length(a) == 0) nothing() else just(max(a))\n}\n\nsafe_sqrt \u003c- function(a) {\n  if (a \u003c 0) nothing() else just(sqrt(a))\n}\n\njust(1:9) %\u003e%\n  and_then(safe_max) %\u003e%\n  and_then(safe_sqrt)\n\nnothing() %\u003e%\n  and_then(safe_max) %\u003e%\n  and_then(safe_sqrt)\n```\n\n## Creating maybe functions\n\nThe maybe package provides another way to create functions that return maybe\nvalues. Instead of rewriting the function to return maybe values we can wrap it \nin the `maybe` function. This will modify the function to return `Nothing` on \nan error or warning. \n\nA predicate function (a function that returns `TRUE` or\n`FALSE`) can be provided as an argument to assert something about the return \nvalue. If the predicate returns `TRUE` then a `Just` value will be returned,\notherwise it will be `Nothing`.\n\n```{r}\nsafe_max \u003c- maybe(max)\nsafe_sqrt \u003c- maybe(sqrt, ensure = not_infinite)\n\nsafe_max(1:9) %\u003e% and_then(safe_sqrt)\nsafe_max(\"hello\") %\u003e% and_then(safe_sqrt)\n```\n\nThis pattern of modifying a function with the `maybe` function and then setting \na default value is so common that there is a shortcut, `perhaps`. The default \nvalue is set with the `default` parameter. This function will always return a \nregular R value, never maybe values.\n\n```{r}\nperhaps_max \u003c- perhaps(max, ensure = is.numeric, default = 0)\n\nperhaps_max(1:9) \nperhaps_max(\"hello\") \n```\n\n## Predicates\n\nMultiple predicates can be combined with the `and`/`or` functions.\n\n```{r}\nsafe_sqrt \u003c- maybe(sqrt, ensure = and(not_nan, not_empty))\n\nsafe_sqrt(9)\nsafe_sqrt(-1)\n```\n\nPredefined combinations are also provided such as `not_undefined`, which ensures \nthat the output is not any of `NULL`, `NA`, `NaN`, `-Inf`, or `Inf`.\n\n```{r}\nsafe_mean \u003c- maybe(mean, ensure = not_undefined)\n\nsafe_mean(c(1, 2, 3))\nsafe_mean(c(NA, 2, 3))\n```\n## Function names\n\nThe names of functions `maybe_map`, `and_then`, `maybe_flatten`, and \n`with_default` are different from the traditional names used for these functions \nin other functional programming languages. If you would like to use the more\ntraditional names aliases are provided.\n\n- `fmap` == `maybe_map`\n- `bind` == `and_then`\n- `join` == `maybe_flatten`\n- `from_maybe` == `with_default`\n\n## Inspiration / Prior work\n\n- [monads R package](https://github.com/hadley/monads)\n- [rmonad R package](https://github.com/arendsee/rmonad)\n- [Maybe Monad in R blog post](https://www.r-bloggers.com/2019/05/maybe-monad-in-r/)\n- [Elm Maybe package](https://package.elm-lang.org/packages/elm/core/1.0.5/Maybe)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farmcn%2Fmaybe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Farmcn%2Fmaybe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farmcn%2Fmaybe/lists"}