Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/egnha/gestalt
Tidy Tools for Making and Combining Functions
https://github.com/egnha/gestalt
anonymous-functions function-composition partial-application quasiquotation
Last synced: 2 days ago
JSON representation
Tidy Tools for Making and Combining Functions
- Host: GitHub
- URL: https://github.com/egnha/gestalt
- Owner: egnha
- License: other
- Created: 2018-05-11T08:59:01.000Z (over 6 years ago)
- Default Branch: master
- Last Pushed: 2022-12-18T10:58:19.000Z (almost 2 years ago)
- Last Synced: 2024-07-31T19:27:38.538Z (4 months ago)
- Topics: anonymous-functions, function-composition, partial-application, quasiquotation
- Language: R
- Homepage:
- Size: 819 KB
- Stars: 38
- Watchers: 4
- Forks: 3
- Open Issues: 10
-
Metadata Files:
- Readme: README.Rmd
- License: LICENSE
Awesome Lists containing this project
README
---
output: github_document
---```{r, echo = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
fig.path = "README-"
)
```[![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)
[![CRAN_Status_Badge](https://www.r-pkg.org/badges/version/gestalt)](https://cran.r-project.org/package=gestalt)# gestalt
The **gestalt** package provides a function composition operator, `%>>>%`, which
improves the clarity, modularity, and versatility of your functions by enabling
you to:- [Express complex functions as chains](#overview) of smaller, more readily
intelligible functions
- Directly manipulate a composite function as a list-like object, so that you
can [inspect](#inspect-or-modify-using-higher-order-functions),
[modify](#inspect-or-modify-using-higher-order-functions), or
[repurpose](#repurpose-using-subset-assignment) any part of the chain of
constituent functionsMore importantly, gestalt fosters a powerful way of thinking about
[values as functions](#the-value-of-values-as-functions).## Overview
The following example (adapted from [purrr](https://purrr.tidyverse.org))
illustrates the use of `%>>>%` to express a function that takes the
name of a factor-column of the `mtcars` data frame, fits a linear model to the
corresponding groups, then computes the R² of the summary.```{r}
library(gestalt)fit <- mpg ~ wt
r2 <- {split(mtcars, mtcars[[.]])} %>>>%
lapply(function(data) lm(!!fit, data)) %>>>%
summarize: (
lapply(summary) %>>>%
stat: sapply(`[[`, "r.squared")
)r2("cyl")
```gestalt leverages the ubiquity of the [magrittr](https://magrittr.tidyverse.org)
`%>%` operator, by adopting its semantics and augmenting it to enable you to:- **Clarify intent** by annotating constituent functions with descriptive names,
which also serve as
[subsetting references](#repurpose-using-subset-assignment)
- **Express nested sub-compositions**, while nonetheless preserving the runtime
characteristics of a flattened composition, so you can focus on expressing
structure that is most natural for your function
- **Unquote sub-expressions** with the tidyverse `!!` operator, to enforce
immutability or spare a runtime computation## Ceci n’est pas une `%>%`
Despite the syntactic similarity, the `%>>>%` operator is conceptually distinct
from the magrittr `%>%` operator. Whereas `%>%` “pipes” a value into a function
to yield a value, `%>>>%` _composes_ functions to yield a function.The most significant distinction, however, is that list idioms apply to
composite functions made by `%>>>%`, so that you can inspect, modify, and
repurpose them, intuitively.### Select segments of functions using indexing
To select the first two functions in `r2`, in order to get the fitted model,
index with the vector `1:2`:```{r}
r2[1:2]("cyl")[["6"]] # Cars with 6 cylinders
```### Repurpose using subset-assignment
To compute the residuals rather than the R², reassign the summary-statistic
function:```{r}
residuals <- r2
residuals$summarize$stat <- function(s) sapply(s, `[[`, "residuals")
residuals("cyl")[["6"]]
```
### Inspect or modify using higher-order functionsConsider a function that capitalizes and joins a random selection of characters:
```{r}
scramble <- sample %>>>% toupper %>>>% paste(collapse = "")set.seed(1)
scramble(letters, 5)
```Here you see the final result of the composition. But because `scramble` is a
list-like object, you can also inspect its intermediate steps by applying a
standard “map-reduce” strategy, such as the following higher-order function:```{r}
stepwise <- lapply(`%>>>%`, print) %>>>% compose
````stepwise` maps over the constituent functions of a composite function to
add printing at each step:```{r}
set.seed(1)
stepwise(scramble)(letters, 5)
```## The [value of values](https://youtu.be/-6BsiVyC1kM) as functions
Whenever you have a value that results from a series of piped values, such as
```{r}
library(magrittr)mtcars %>%
split(.$cyl) %>%
lapply(function(data) lm(mpg ~ wt, data)) %>%
lapply(summary) %>%
sapply(`[[`, "r.squared")
```you can transpose it to a **constant composite function** that computes the same
value, simply by treating the input value as a constant function and replacing
each function application, `%>%`, by function composition, `%>>>%`:```{r}
R2 <- {mtcars} %>>>%
split(.$cyl) %>>>%
lapply(function(data) lm(mpg ~ wt, data)) %>>>%
lapply(summary) %>>>%
sapply(`[[`, "r.squared")
```You gain power by treating (piped) values as (composite) functions:
1. **Values as functions are lazy**. You can separate the value’s declaration
from its point of use—the value is only computed on demand:
```{r}
R2()
```
2. **Values as functions are cheap**. You can cache the value of `R2` by
declaring it as a constant:
```{r, eval = FALSE}
R2 <- constant(R2)
R2()
#> 4 6 8
#> 0.5086326 0.4645102 0.4229655
# On a 2015 vintage laptop
microbenchmark::microbenchmark(R2(), times = 1e6)
#> Unit: nanoseconds
#> expr min lq mean median uq max neval
#> R2() 532 567 709.1435 585 647 39887308 1e+06
```
2. **Values as functions encode their computation**. Since a composite function
qua computation is a list-like object, you can compute on it to extract
**latent information**.
For instance, you can get the normal Q–Q plot of the fitted model for
6-cylinder cars from the head of `R2`:
```{r, eval = FALSE}
head(R2, 3)() %>% .[["6"]] %>% plot(2)
```
## ComplementsIn conjunction with `%>>>%`, gestalt also provides:
- `fn`, a more concise and flexible variation of `function`, which supports
tidyverse quasiquotation.
```{r}
size <- 5L
fn(x, ... ~ sample(x, !!size, ...))
```
- `partial`, to make new functions from old by fixing a number of arguments,
i.e.,
[partial application](https://en.wikipedia.org/wiki/Partial_application).
Like `fn`, it also supports quasiquotation.
```{r}
(draw <- partial(sample, size = !!size, replace = TRUE))
set.seed(2)
draw(letters)
```
Additionally, `partial` is:
* **Hygenic**: The fixed argument values are
[tidily evaluated](https://rlang.r-lib.org/reference/eval_tidy.html)
promises; in particular, the usual lazy behavior of function arguments,
which can be overridden via unquoting, is respected even for fixed
arguments.
* **Flat**: Fixing arguments in stages is _operationally_ equivalent to
fixing them all at once—you get the same function either way:
```{r}
partial(partial(sample, replace = TRUE), size = 5L)
```
See the package documentation for more details (`help(package = gestalt)`).## Installation
Install from [CRAN](https://cran.r-project.org/package=gestalt):
```{r, eval = FALSE}
install.packages("gestalt")
```Alternatively, install the development version from GitHub:
```{r gh-installation, eval = FALSE}
# install.packages("devtools")
devtools::install_github("egnha/gestalt", build_vignettes = TRUE)
```## Acknowledgments
- The core semantics of `%>>>%` conform to those of the
[magrittr](https://magrittr.tidyverse.org) `%>%` operator developed by
[Stefan Milton Bache](https://github.com/smbache).
- The engine for quasiquotation and expression capture is powered by the
[rlang](https://rlang.r-lib.org) package by
[Lionel Henry](https://github.com/lionel-) and
[Hadley Wickham](https://github.com/hadley).
- The “triple arrow” notation for the composition operator is taken from the
Haskell
[Control.Arrow](https://hackage.haskell.org/package/base/docs/Control-Arrow.html)
library by Ross Paterson.## License
MIT Copyright © 2018–2022 [Eugene Ha](https://github.com/egnha)