expandChain: Expand code objects

View source: R/metareactive.R

expandChainR Documentation

Expand code objects

Description

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.

Usage

newExpansionContext()

expandChain(..., .expansionContext = newExpansionContext())

Arguments

...

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.

.expansionContext

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.

Details

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:

    nums <- metaReactive({ runif(100) })
    obs <- metaObserve({
      summary(..(nums()))
      hist(..(nums()))
    })

When code is extracted using withMetaMode:

    withMetaMode(obs())

The result looks like this:

    summary(runif(100))
    plot(runif(100))

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).

    expandChain(obs())

The result looks like this:

    nums <- runif(100)
    summary(nums)
    plot(nums)

You can pass multiple meta objects and/or comments to expandChain.

    expandChain(
      "# Generate values",
      nums(),
      "# Summarize and plot",
      obs()
    )

Output:

    # Load data
    nums <- runif(100)
    nums
    # Inspect data
    summary(nums)
    plot(nums)

You can suppress the printing of the nums vector in the previous example by wrapping the nums() argument to expandChain() with invisible(nums()).

Value

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.

Preserving dependencies between expandChain() calls

Sometimes 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.

Substituting metaReactive objects

Sometimes, 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:

    data <- read.csv("data.csv")
    summary(data)

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).

References

https://rstudio.github.io/shinymeta/articles/code-generation.html

Examples

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())

# Explicitly print top
expandChain(top(), obs())

# Separate into two code chunks
exp_ctx <- newExpansionContext()
expandChain(.expansionContext = exp_ctx,
  invisible(top()),
  invisible(bottom()))
expandChain(.expansionContext = exp_ctx,
  obs())


rstudio/shinymeta documentation built on Oct. 25, 2023, 7:12 p.m.