Use expandChain
to write code out of one or more metaReactive objects.
Each meta-reactive object (expression, observer, or renderer) will cause not
only its own code to be written, but that of its dependencies as well.
newExpansionContext()
expandChain(..., .expansionContext = newExpansionContext())
All arguments must be unnamed, and must be one of: 1) calls to
meta-reactive objects, 2) comment string (e.g. "# A comment"
), 3)
language object (e.g. quote(print(1 + 1))
), or 4) NULL
(which will be
ignored). Calls to meta-reactive objects can optionally be invisible()
,
see Details.
Accept the default value if calling expandChain
a
single time to generate a corpus of code; or create an expansion context
object using newExpansionContext()
and pass it to multiple related calls
of expandChain
. See Details.
The return value of expandChain()
is a code object that's suitable for
printing or passing to displayCodeModal()
, buildScriptBundle()
, or
buildRmdBundle()
.
The return value of newExpansionContext
is an object that should be
passed to multiple expandChain()
calls.
There are two ways to extract code from meta objects (i.e. metaReactive()
,
metaObserve()
, and metaRender()
): withMetaMode()
and expandChain()
.
The simplest is withMetaMode(obj())
, which crawls the tree of meta-reactive
dependencies and expands each ..()
in place.
For example, consider these meta objects:
When code is extracted using withMetaMode
:
The result looks like this:
Notice how runif(100)
is inlined wherever ..(nums())
appears, which is not desirable if we wish to reuse the same
values for summary()
and plot()
.
The expandChain
function helps us workaround this issue
by assigning return values of metaReactive()
expressions to
a name, then replaces relevant expansion (e.g., ..(nums())
)
with the appropriate name (e.g. nums
).
The result looks like this:
You can pass multiple meta objects and/or comments to expandChain
.
Output:
You can suppress the printing of the nums
vector in the previous example by
wrapping the nums()
argument to expandChain()
with invisible(nums())
.
expandChain()
callsSometimes we may have related meta objects that we want to generate code for, but we want the code for some objects in one code chunk, and the code for other objects in another code chunk; for example, you might be constructing an R Markdown report that has a specific place for each code chunk.
Within a single expandChain()
call, all metaReactive
objects are
guaranteed to only be declared once, even if they're declared on by multiple
meta objects; but since we're making two expandChain()
calls, we will end
up with duplicated code. To remove this duplication, we need the second
expandChain
call to know what code was emitted in the first expandChain
call.
We can achieve this by creating an "expansion context" and sharing it between the two calls.
exp_ctx <- newExpansionContext()
chunk1 <- expandChain(.expansionContext = exp_ctx,
invisible(nums())
)
chunk2 <- expandChain(.expansionContext = exp_ctx,
obs()
)
After this code is run, chunk1
contains only the definition of nums
and
chunk2
contains only the code for obs
.
metaReactive
objectsSometimes, when generating code, we want to completely replace the
implementation of a metaReactive
. For example, our Shiny app might contain
this logic, using shiny::fileInput()
:
data <- metaReactive2({
req(input$file_upload)
metaExpr(read.csv(..(input$file_upload$datapath)))
})
obs <- metaObserve({
summary(..(data()))
})
Shiny's file input works by saving uploading files to a temp directory. The
file referred to by input$file_upload$datapath
won't be available when
another user tries to run the generated code.
You can use the expansion context object to swap out the implementation of
data
, or any other metaReactive
:
ec <- newExpansionContext()
ec$substituteMetaReactive(data, function() {
metaExpr(read.csv("data.csv"))
})
expandChain(.expansionContext = ec, obs())
Result:
Just make sure this code ends up in a script or Rmd bundle that includes the
uploaded file as data.csv
, and the user will be able to reproduce your
analysis.
The substituteMetaReactive
method takes two arguments: the metaReactive
object to substitute, and a function that takes zero arguments and returns a
quoted expression (for the nicest looking results, use metaExpr
to create
the expression). This function will be invoked the first time the
metaReactive
object is encountered (or if the metaReactive
is defined
with inline = TRUE
, then every time it is encountered).
input <- list(dataset = "cars")
# varname is only required if srcref aren't supported
# (R CMD check disables them for some reason?)
mr <- metaReactive({
get(..(input$dataset), "package:datasets")
})
top <- metaReactive({
head(..(mr()))
})
bottom <- metaReactive({
tail(..(mr()))
})
obs <- metaObserve({
message("Top:")
summary(..(top()))
message("Bottom:")
summary(..(bottom()))
})
# Simple case
expandChain(obs())
#> mr <- get("cars", "package:datasets")
#> top <- head(mr)
#> bottom <- tail(mr)
#> message("Top:")
#> summary(top)
#> message("Bottom:")
#> summary(bottom)
# Explicitly print top
expandChain(top(), obs())
#> mr <- get("cars", "package:datasets")
#> top <- head(mr)
#> top
#> bottom <- tail(mr)
#> message("Top:")
#> summary(top)
#> message("Bottom:")
#> summary(bottom)
# Separate into two code chunks
exp_ctx <- newExpansionContext()
expandChain(.expansionContext = exp_ctx,
invisible(top()),
invisible(bottom()))
#> mr <- get("cars", "package:datasets")
#> top <- head(mr)
#> bottom <- tail(mr)
expandChain(.expansionContext = exp_ctx,
obs())
#> message("Top:")
#> summary(top)
#> message("Bottom:")
#> summary(bottom)