{"id":13400954,"url":"https://github.com/egnha/gestalt","last_synced_at":"2025-06-20T23:36:40.799Z","repository":{"id":56934143,"uuid":"133015920","full_name":"egnha/gestalt","owner":"egnha","description":"Tidy Tools for Making and Combining Functions","archived":false,"fork":false,"pushed_at":"2022-12-18T10:58:19.000Z","size":839,"stargazers_count":38,"open_issues_count":10,"forks_count":3,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-05-02T05:46:19.954Z","etag":null,"topics":["anonymous-functions","function-composition","partial-application","quasiquotation"],"latest_commit_sha":null,"homepage":"","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/egnha.png","metadata":{"files":{"readme":"README.Rmd","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-05-11T08:59:01.000Z","updated_at":"2024-06-21T18:38:13.000Z","dependencies_parsed_at":"2023-01-29T19:02:47.913Z","dependency_job_id":null,"html_url":"https://github.com/egnha/gestalt","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/egnha/gestalt","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/egnha%2Fgestalt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/egnha%2Fgestalt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/egnha%2Fgestalt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/egnha%2Fgestalt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/egnha","download_url":"https://codeload.github.com/egnha/gestalt/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/egnha%2Fgestalt/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261037152,"owners_count":23100933,"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":["anonymous-functions","function-composition","partial-application","quasiquotation"],"created_at":"2024-07-30T19:00:57.269Z","updated_at":"2025-06-20T23:36:35.785Z","avatar_url":"https://github.com/egnha.png","language":"R","funding_links":[],"categories":["R"],"sub_categories":[],"readme":"---\noutput: github_document\n---\n\n\u003c!-- README.md is generated from README.Rmd. Please edit that file --\u003e\n\n```{r, echo = FALSE}\nknitr::opts_chunk$set(\n  collapse = TRUE,\n  comment = \"#\u003e\",\n  fig.path = \"README-\"\n)\n```\n\n\u003c!-- badges: start --\u003e\n[![R-CMD-check](https://github.com/egnha/gestalt/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/egnha/gestalt/actions/workflows/R-CMD-check.yaml)\n[![CRAN_Status_Badge](https://www.r-pkg.org/badges/version/gestalt)](https://cran.r-project.org/package=gestalt)\n\u003c!-- badges: end --\u003e\n\n# gestalt\n\nThe **gestalt** package provides a function composition operator, `%\u003e\u003e\u003e%`, which\nimproves the clarity, modularity, and versatility of your functions by enabling\nyou to:\n\n - [Express complex functions as chains](#overview) of smaller, more readily\n   intelligible functions\n   \n - Directly manipulate a composite function as a list-like object, so that you\n   can [inspect](#inspect-or-modify-using-higher-order-functions),\n   [modify](#inspect-or-modify-using-higher-order-functions), or\n   [repurpose](#repurpose-using-subset-assignment) any part of the chain of\n   constituent functions\n\nMore importantly, gestalt fosters a powerful way of thinking about\n[values as functions](#the-value-of-values-as-functions).\n\n## Overview\n\nThe following example (adapted from [purrr](https://purrr.tidyverse.org))\nillustrates the use of `%\u003e\u003e\u003e%` to express a function that takes the\nname of a factor-column of the `mtcars` data frame, fits a linear model to the\ncorresponding groups, then computes the R² of the summary.\n\n```{r}\nlibrary(gestalt)\n\nfit \u003c- mpg ~ wt\n\nr2 \u003c- {split(mtcars, mtcars[[.]])} %\u003e\u003e\u003e%\n  lapply(function(data) lm(!!fit, data)) %\u003e\u003e\u003e%\n  summarize: (\n    lapply(summary) %\u003e\u003e\u003e%\n      stat: sapply(`[[`, \"r.squared\")\n  )\n\nr2(\"cyl\")\n```\n\ngestalt leverages the ubiquity of the [magrittr](https://magrittr.tidyverse.org)\n`%\u003e%` operator, by adopting its semantics and augmenting it to enable you to:\n\n - **Clarify intent** by annotating constituent functions with descriptive names,\n   which also serve as\n   [subsetting references](#repurpose-using-subset-assignment)\n   \n - **Express nested sub-compositions**, while nonetheless preserving the runtime \n   characteristics of a flattened composition, so you can focus on expressing\n   structure that is most natural for your function\n   \n - **Unquote sub-expressions** with the tidyverse `!!` operator, to enforce\n   immutability or spare a runtime computation\n\n## Ceci n’est pas une `%\u003e%`\n\nDespite the syntactic similarity, the `%\u003e\u003e\u003e%` operator is conceptually distinct\nfrom the magrittr `%\u003e%` operator. Whereas `%\u003e%` “pipes” a value into a function\nto yield a value, `%\u003e\u003e\u003e%` _composes_ functions to yield a function.\n\nThe most significant distinction, however, is that list idioms apply to\ncomposite functions made by `%\u003e\u003e\u003e%`, so that you can inspect, modify, and\nrepurpose them, intuitively.\n\n### Select segments of functions using indexing\n\nTo select the first two functions in `r2`, in order to get the fitted model,\nindex with the vector `1:2`:\n\n```{r}\nr2[1:2](\"cyl\")[[\"6\"]]  # Cars with 6 cylinders\n```\n\n### Repurpose using subset-assignment\n\nTo compute the residuals rather than the R², reassign the summary-statistic\nfunction:\n\n```{r}\nresiduals \u003c- r2\nresiduals$summarize$stat \u003c- function(s) sapply(s, `[[`, \"residuals\")\nresiduals(\"cyl\")[[\"6\"]]\n```\n    \n### Inspect or modify using higher-order functions\n\nConsider a function that capitalizes and joins a random selection of characters:\n\n```{r}\nscramble \u003c- sample %\u003e\u003e\u003e% toupper %\u003e\u003e\u003e% paste(collapse = \"\")\n\nset.seed(1)\nscramble(letters, 5)\n```\n\nHere you see the final result of the composition. But because `scramble` is a\nlist-like object, you can also inspect its intermediate steps by applying a\nstandard “map-reduce” strategy, such as the following higher-order function:\n\n```{r}\nstepwise \u003c- lapply(`%\u003e\u003e\u003e%`, print) %\u003e\u003e\u003e% compose\n```\n\n`stepwise` maps over the constituent functions of a composite function to\nadd printing at each step:\n\n```{r}\nset.seed(1)\nstepwise(scramble)(letters, 5)\n```\n\n## The [value of values](https://youtu.be/-6BsiVyC1kM) as functions\n\nWhenever you have a value that results from a series of piped values, such as\n\n```{r}\nlibrary(magrittr)\n\nmtcars %\u003e% \n  split(.$cyl) %\u003e% \n  lapply(function(data) lm(mpg ~ wt, data)) %\u003e% \n  lapply(summary) %\u003e% \n  sapply(`[[`, \"r.squared\")\n```\n\nyou can transpose it to a **constant composite function** that computes the same\nvalue, simply by treating the input value as a constant function and replacing\neach function application, `%\u003e%`, by function composition, `%\u003e\u003e\u003e%`:\n\n```{r}\nR2 \u003c- {mtcars} %\u003e\u003e\u003e% \n  split(.$cyl) %\u003e\u003e\u003e%\n  lapply(function(data) lm(mpg ~ wt, data)) %\u003e\u003e\u003e%\n  lapply(summary) %\u003e\u003e\u003e%\n  sapply(`[[`, \"r.squared\")\n```\n\nYou gain power by treating (piped) values as (composite) functions:\n\n 1. **Values as functions are lazy**. You can separate the value’s declaration\n    from its point of use—the value is only computed on demand:\n    ```{r}\n    R2()\n    ```\n    \n 2. **Values as functions are cheap**. You can cache the value of `R2` by\n    declaring it as a constant:\n    ```{r, eval = FALSE}\n    R2 \u003c- constant(R2)\n    R2()\n    #\u003e         4         6         8 \n    #\u003e 0.5086326 0.4645102 0.4229655\n    \n    # On a 2015 vintage laptop\n    microbenchmark::microbenchmark(R2(), times = 1e6)  \n    #\u003e Unit: nanoseconds\n    #\u003e  expr min  lq     mean median  uq      max neval\n    #\u003e  R2() 532 567 709.1435    585 647 39887308 1e+06\n    ```\n  \n 2. **Values as functions encode their computation**. Since a composite function\n    qua computation is a list-like object, you can compute on it to extract\n    **latent information**.\n    \n    For instance, you can get the normal Q–Q plot of the fitted model for\n    6-cylinder cars from the head of `R2`:\n    ```{r, eval = FALSE}\n    head(R2, 3)() %\u003e% .[[\"6\"]] %\u003e% plot(2)\n    ```\n    \u003cimg src=\"inst/images/plot.svg\"/\u003e\n    \n## Complements\n\nIn conjunction with `%\u003e\u003e\u003e%`, gestalt also provides:\n\n - `fn`, a more concise and flexible variation of `function`, which supports\n   tidyverse quasiquotation.\n    ```{r}\n    size \u003c- 5L\n    \n    fn(x, ... ~ sample(x, !!size, ...))\n    ```\n    \n - `partial`, to make new functions from old by fixing a number of arguments,\n   i.e.,\n   [partial application](https://en.wikipedia.org/wiki/Partial_application).\n   Like `fn`, it also supports quasiquotation.\n    ```{r}\n    (draw \u003c- partial(sample, size = !!size, replace = TRUE))\n    \n    set.seed(2)\n    draw(letters)\n    ```\n    Additionally, `partial` is:\n    \n    * **Hygenic**: The fixed argument values are\n      [tidily evaluated](https://rlang.r-lib.org/reference/eval_tidy.html)\n      promises; in particular, the usual lazy behavior of function arguments,\n      which can be overridden via unquoting, is respected even for fixed\n      arguments.\n      \n    * **Flat**: Fixing arguments in stages is _operationally_ equivalent to\n      fixing them all at once—you get the same function either way:\n      ```{r}\n      partial(partial(sample, replace = TRUE), size = 5L)\n      ```\n \nSee the package documentation for more details (`help(package = gestalt)`).\n\n## Installation\n\nInstall from [CRAN](https://cran.r-project.org/package=gestalt):\n\n```{r, eval = FALSE}\ninstall.packages(\"gestalt\")\n```\n\nAlternatively, install the development version from GitHub:\n\n```{r gh-installation, eval = FALSE}\n# install.packages(\"devtools\")\ndevtools::install_github(\"egnha/gestalt\", build_vignettes = TRUE)\n```\n\n## Acknowledgments\n\n - The core semantics of `%\u003e\u003e\u003e%` conform to those of the \n   [magrittr](https://magrittr.tidyverse.org) `%\u003e%` operator developed by\n   [Stefan Milton Bache](https://github.com/smbache).\n   \n - The engine for quasiquotation and expression capture is powered by the\n   [rlang](https://rlang.r-lib.org) package by\n   [Lionel Henry](https://github.com/lionel-) and\n   [Hadley Wickham](https://github.com/hadley).\n   \n - The “triple arrow” notation for the composition operator is taken from the\n   Haskell\n   [Control.Arrow](https://hackage.haskell.org/package/base/docs/Control-Arrow.html)\n   library by Ross Paterson.\n\n## License\n\nMIT Copyright © 2018–2022 [Eugene Ha](https://github.com/egnha)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fegnha%2Fgestalt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fegnha%2Fgestalt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fegnha%2Fgestalt/lists"}