| compose {gestalt} | R Documentation |
Compose Functions
Description
To compose functions,
Use
compose():compose(f, g, h, ...)
This makes the function that applies
f, theng, thenh, etc. It has the formals of the first function applied (namelyf). For example, iffun <- compose(paste, toupper)
then the function
fun()has the same signature aspaste(), and the callfun(letters, collapse = ",")
is equivalent to the composite call
toupper(paste(letters, collapse = ","))
Use
`%>>>%`:f %>>>% g %>>>% h %>>>% ...
It comprehends both the semantics of the magrittr
`%>%`operator and quasiquotation. For example, ifsep <- "" fun <- sample %>>>% paste(collapse = !!sep)
then the function
fun()has the same signature assample(), and the callfun(x, size, replace, prob)
is equivalent to the composite call
paste(sample(x, size, replace, prob), collapse = "")
Use as.list() to recover the list of composite functions. For example, both
as.list(compose(paste, capitalize = toupper)) as.list(paste %>>>% capitalize: toupper)
return the (named) list of functions list(paste, capitalize = toupper).
Usage
compose(...)
fst %>>>% snd
Arguments
... |
Functions or lists thereof to compose, in order of application.
Lists of functions are automatically spliced in.
Unquoting of names, via |
fst, snd |
Functions. These may be optionally named using a colon ( |
Value
Function of class CompositeFunction, whose
formals are those of the first function applied (as a
closure).
Semantics of the Composition Operator
The `%>>>%` operator adopts the semantics of the
magrittr `%>%`
operator:
-
Bare names are matched to functions: For example, in a composition like
... %>>>% foo %>>>% ...
the ‘
foo’ is matched to the function of that name. -
Function calls are interpreted as a unary function of a point (
.): A call is interpreted as a function (of a point) in one of two ways:If the point matches an argument value, the call is literally interpreted as the body of the function. For example, in the compositions
... %>>>% foo(x, .) %>>>% ... ... %>>>% foo(x, y = .) %>>>% ...
the ‘
foo(x, .)’, resp. ‘foo(x, y = .)’, is interpreted as the functionfunction(..., . = ..1) foo(x, .), resp.function(..., . = ..1) foo(x, y = .).Otherwise, the call is regarded as implicitly having the point as its first argument before being interpreted as the body of the function. For example, in the compositions
... %>>>% foo(x) %>>>% ... ... %>>>% foo(x, y(.)) %>>>% ...
the ‘
foo(x)’, resp. ‘foo(x, y(.))’, is interpreted as the functionfunction(..., . = ..1) foo(., x), resp.function(..., . = ..1) foo(., x, y(.)).
-
Expressions
{...}are interpreted as a function of a point (.): For example, in a composition... %>>>% { foo(.) bar(.) } %>>>% ...the ‘
{foo(.); bar(.)}’ is interpreted as the functionfunction(..., . = ..1) {foo(.); bar(.)}.Curly braces are useful when you need to circumvent
`%>>>%`'s usual interpretation of function calls. For example, in a composition... %>>>% {foo(x, y(.))} %>>>% ...the ‘
{foo(x, y(.))}’ is interpreted as the functionfunction(..., . = ..1) foo(x, y(.)). There is no point as first argument tofoo.
Exceptions to the Interpretation of Calls as Functions
As a matter of convenience, some exceptions are made to the above interpretation of calls as functions:
-
Parenthesis (
() applies grouping. (In R,`(`is indeed a function.) In particular, expressions within parentheses are literally interpreted. -
Colon (
:) applies naming, according to the syntax ‘<name>: <function>’, where ‘<function>’ is interpreted according to the semantics of`%>>>%`. For example, in... %>>>% aName: foo %>>>% ...
the function
foois named"aName". -
fn(), namespace operators (`::`,`:::`) and extractors (`$`,`[[`,`[`) are literally interpreted. This allows for list extractors to be applied to composite functions appearing in a`%>>>%`call (see 'Operate on Composite Functions as List-Like Objects'). For example, the compositionspaste %>>>% tolower paste %>>>% base::tolower (paste %>>>% toupper)[[1]] %>>>% tolower
are equivalent functions.
Quasiquotation
The `%>>>%` operator supports Tidyverse
unquoting (via !!). Use it to:
-
Enforce immutability: For example, by unquoting
resinres <- "result" get_result <- identity %>>>% lapply(`[[`, !!res)
you ensure that the function
get_result()always extracts the component named"result", even if the bindingreschanges its value or is removed altogether. -
Interpret the point (
.) in the lexical scope: Even though`%>>>%`interprets ‘.’ as a function argument, you can still reference an object of that name via unquoting. For example,. <- "point" is_point <- identity %>>>% {. == !!.}determines a function that checks for equality with the string
"point". -
Name composite functions, programmatically: For example, unquoting
nminnm <- "aName" ... %>>>% !!nm: foo %>>>% ...
names the ‘
foo’-component of the resulting composite function"aName". -
Accelerate functions by fixing constant dependencies: For example, presuming the value of the call
f()is constant and thatgis a pure function (meaning that its return value depends only on its input), both... %>>>% g(f()) %>>>% ... ... %>>>% g(!!f()) %>>>% ...
would be functions yielding the same values. But the first would compute
f()anew with each call, whereas the second would simply depend on a fixed, pre-computed value off().
Operate on Composite Functions as List-Like Objects
You can think of a composite function as embodying the (possibly nested) structure of its list of constituent functions. In fact, you can apply familiar index and assignment operations to a composite function, as if it were this list, getting a function in return. This enables you to leverage composite functions as structured computations.
Indexing
For instance, the ‘sum’ in the following composite function
f <- abs %>>>% out: (log %>>>% agg: sum)
can be extracted in the usual ways:
f[[2]][[2]]
f[[c(2, 2)]]
f$out$agg
f[["out"]][["agg"]]
f[["out"]]$agg
f$out[[2]]
f[[list("out", 2)]]
The last form of indexing with a mixed list is handy when you need to create an index programmatically.
Additionally, you can excise sub-composite functions with
[, head(), tail(). For example:
Both
f[1]andhead(f, 1)get the ‘abs’ as a composite function, namelycompose(abs)-
f[2:1]reverses the order of the top-level functions to yieldout: (log %>>>% agg: sum) %>>>% abs
-
f$out[c(FALSE, TRUE)]gets the ‘sum’ as a (named) composite function
Subset Assignment
Similarily, subset assignment works as it does for lists. For instance, you
can replace the ‘sum’ with the identity function:
f[[2]][[2]] <- identity
f$out$agg <- identity
f[["out"]][["agg"]] <- identity
f$out[[2]] <- identity
f[[list("out", 2)]] <- identity
Multiple constituent functions can be reassigned using
[<-. For example
f[2] <- list(log) f["out"] <- list(log) f[c(FALSE, TRUE)] <- list(log)
all replace the second constituent function with log, so that f becomes
abs %>>>% log.
Other List Methods
The generic methods unlist(), length(), names() also apply to
composite functions. In conjunction with compose(), you can use
unlist() to “flatten” compositions. For example
compose(unlist(f, use.names = FALSE))
gives a function that is identical to
abs %>>>% log %>>>% sum
Composite Functions Balance Speed and Complexity
The speed of a composite function made by compose() or `%>>>%`
(regardless of its nested depth) is on par with a manually constructed
serial composition. This is because compose() and `%>>>%` are
associative, semantically and operationally. For instance, triple
compositions,
compose(f, g, h) f %>>>% g %>>>% h compose(f, compose(g, h)) f %>>>% (g %>>>% h) compose(compose(f, g), h) (f %>>>% g) %>>>% h
are all implemented as the same function. Lists of functions are automatically “flattened” when composed.
Nevertheless, the original nested structure of constituent functions is
faithfully recovered by as.list(). In particular, as.list() and
compose() are mutually invertible: as.list(compose(fs)) is the same
as fs, when fs is a (nested) list of functions. (But note that the
names of the list of composite functions is always a character vector; it
is never NULL.)
See Also
constant(); combined with `%>>>%`, this provides a lazy,
structured alternative to the
magrittr `%>%`
operator.
Examples
# Functions are applied in the order in which they are listed
inv <- partial(`/`, 1) # reciprocal
f0 <- compose(abs, log, inv)
stopifnot(all.equal(f0(-2), 1 / log(abs(-2))))
# Alternatively, compose using the `%>>>%` operator
f1 <- abs %>>>% log %>>>% {1 / .}
stopifnot(all.equal(f1(-2), f0(-2)))
## Not run:
# Transform a function to a JSON function
library(jsonlite)
# By composing higher-order functions:
jsonify <- {fromJSON %>>>% .} %>>>% {. %>>>% toJSON}
# By directly composing with input/output transformers:
jsonify <- fn(f ~ fromJSON %>>>% f %>>>% toJSON)
## End(Not run)
# Formals of initial function are preserved
add <- function(a, b = 0) a + b
stopifnot(identical(formals(compose(add, inv)), formals(add)))
# Compositions can be provided by lists, in several equivalent ways
f2 <- compose(list(abs, log, inv))
f3 <- compose(!!! list(abs, log, inv))
f4 <- compose(abs, list(log, inv))
f5 <- compose(abs, !!! list(log, inv))
stopifnot(
all.equal(f2, f0), all.equal(f2(-2), f0(-2)),
all.equal(f3, f0), all.equal(f3(-2), f0(-2)),
all.equal(f4, f0), all.equal(f4(-2), f0(-2)),
all.equal(f5, f0), all.equal(f5(-2), f0(-2))
)
# compose() and as.list() are mutally invertible
f6 <- compose(abs, as.list(compose(log, inv)))
stopifnot(
all.equal(f6, f0), all.equal(f6(-2), f0(-2))
)
fs <- list(abs, log, inv)
stopifnot(all.equal(check.attributes = FALSE,
as.list(compose(fs)), fs,
))
# `%>>>%` supports names, magrittr `%>%` semantics, and quasiquotation
sep <- ""
scramble <- shuffle: sample %>>>% paste(collapse = !!sep)
nonsense <- scramble(letters)
stopifnot(
nchar(nonsense) == 26L,
identical(letters, sort(strsplit(nonsense, sep)[[1]])),
identical(scramble$shuffle, sample)
)