knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.path = "README-" )
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 of smaller, more readily intelligible functions
Directly manipulate a composite function as a list-like object, so that you can inspect, modify, or repurpose any part of the chain of constituent functions
More importantly, gestalt fosters a powerful way of thinking about values as functions.
The following example (adapted from purrr)
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.
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
%>%
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
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
%>%
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.
To select the first two functions in r2
, in order to get the fitted model,
index with the vector 1:2
:
r2[1:2]("cyl")[["6"]] # Cars with 6 cylinders
To compute the residuals rather than the R², reassign the summary-statistic function:
residuals <- r2 residuals$summarize$stat <- function(s) sapply(s, `[[`, "residuals") residuals("cyl")[["6"]]
Consider a function that capitalizes and joins a random selection of characters:
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:
stepwise <- lapply(`%>>>%`, print) %>>>% compose
stepwise
maps over the constituent functions of a composite function to
add printing at each step:
set.seed(1) stepwise(scramble)(letters, 5)
Whenever you have a value that results from a series of piped values, such as
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, %>>>%
:
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:
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()
Values as functions are cheap. You can cache the value of R2
by
declaring it as a constant:
```r
R2 <- constant(R2)
R2()
#> 4 6 8
#> 0.5086326 0.4645102 0.4229655
microbenchmark::microbenchmark(R2(), times = 1e6)
```
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
head(R2, 3)() %>% .[["6"]] %>% plot(2)
In 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.
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 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)
).
Install from CRAN:
install.packages("gestalt")
Alternatively, install the development version from GitHub:
# install.packages("devtools") devtools::install_github("egnha/gestalt", build_vignettes = TRUE)
The core semantics of %>>>%
conform to those of the
magrittr %>%
operator developed by
Stefan Milton Bache.
The engine for quasiquotation and expression capture is powered by the rlang package by Lionel Henry and Hadley Wickham.
The “triple arrow” notation for the composition operator is taken from the Haskell Control.Arrow library by Ross Paterson.
MIT Copyright © 2018–2022 Eugene Ha
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.