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
foo
is 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
res
inres <- "result" get_result <- identity %>>>% lapply(`[[`, !!res)
you ensure that the function
get_result()
always extracts the component named"result"
, even if the bindingres
changes 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
nm
innm <- "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 thatg
is 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)
)