10.5 Function factories + functionals
To finish off the chapter, I’ll show how you might combine functionals and function factories to turn data into many functions. The following code creates many specially named power functions by iterating over a list of arguments:
list(
names <-square = 2,
cube = 3,
root = 1/2,
cuberoot = 1/3,
reciprocal = -1
) purrr::map(names, power1)
funs <-
$root(64)
funs#> [1] 8
$root
funs#> function(x) {
#> x ^ exp
#> }
#> <bytecode: 0x558261e8c8d0>
#> <environment: 0x5582648878f8>
This idea extends in a straightforward way if your function factory takes two (replace map()
with map2()
) or more (replace with pmap()
) arguments.
One downside of the current construction is that you have to prefix every function call with funs$
. There are three ways to eliminate this additional syntax:
For a very temporary effect, you can use
with()
:with(funs, root(100)) #> [1] 10
I recommend this because it makes it very clear when code is being executed in a special context and what that context is.
For a longer effect, you can
attach()
the functions to the search path, thendetach()
when you’re done:attach(funs) #> The following objects are masked _by_ .GlobalEnv: #> #> cube, square root(100) #> [1] 10 detach(funs)
You’ve probably been told to avoid using
attach()
, and that’s generally good advice. However, the situation is a little different to the usual because we’re attaching a list of functions, not a data frame. It’s less likely that you’ll modify a function than a column in a data frame, so the some of the worst problems withattach()
don’t apply.Finally, you could copy the functions to the global environment with
env_bind()
(you’ll learn about!!!
in Section 19.6). This is mostly permanent:::env_bind(globalenv(), !!!funs) rlangroot(100) #> [1] 10
You can later unbind those same names, but there’s no guarantee that they haven’t been rebound in the meantime, and you might be deleting an object that someone else created.
::env_unbind(globalenv(), names(funs)) rlang
You’ll learn an alternative approach to the same problem in Section 19.7.4. Instead of using a function factory, you could construct the function with quasiquotation. This requires additional knowledge, but generates functions with readable bodies, and avoids accidentally capturing large objects in the enclosing scope. We use that idea in Section 21.2.4 when we work on tools for generating HTML from R.
10.5.1 Exercises
Which of the following commands is equivalent to
with(x, f(z))
?x$f(x$z)
.f(x$z)
.x$f(z)
.f(z)
.- It depends.
Compare and contrast the effects of
env_bind()
vs.attach()
for the following code.list( funs <-mean = function(x) mean(x, na.rm = TRUE), sum = function(x) sum(x, na.rm = TRUE) ) attach(funs) #> The following objects are masked from package:base: #> #> mean, sum function(x) stop("Hi!") mean <-detach(funs) env_bind(globalenv(), !!!funs) function(x) stop("Hi!") mean <-env_unbind(globalenv(), names(funs))