Skip to contents

[Stable]

Create a qenv object and evaluate code in it to track code history.

Usage

qenv()

eval_code(object, code)

# S3 method for class 'qenv'
x[names, ...]

get_code(object, deparse = TRUE, names = NULL, ...)

# S3 method for class 'qenv'
within(data, expr, ...)

Arguments

object

(qenv)

code

(character, language or expression) code to evaluate. It is possible to preserve original formatting of the code by providing a character or an expression being a result of parse(keep.source = TRUE).

x

(qenv)

names

(character) for x[names], names of objects included in qenv to subset. Names not present in qenv are skipped. For get_code [Experimental] vector of object names to return the code for. For more details see the "Extracting dataset-specific code" section.

...

see Details

deparse

(logical(1)) flag specifying whether to return code as character or expression.

data

(qenv)

expr

(expression) to evaluate. Must be inline code, see Using language objects...

Value

qenv returns a qenv object.

eval_code returns a qenv object with expr evaluated or qenv.error if evaluation fails.

[[, $ and get return the value of the object named name in the qenv object.

names return a character vector of all the names of the objects in the qenv object.

ls return a character vector of the names of the objects in the qenv object. It will only show the objects that are not named with a dot prefix, unless the all.names = TRUE, which will show all objects.

get_code returns the traced code in the form specified by deparse.

within returns a qenv object with expr evaluated or qenv.error if evaluation fails.

Details

qenv() instantiates a qenv with an empty environment. Any changes must be made by evaluating code in it with eval_code or within, thereby ensuring reproducibility.

eval_code() evaluates given code in the qenv environment and appends it to the code slot. Thus, if the qenv had been instantiated empty, contents of the environment are always a result of the stored code.

x[[name]], x$name and get(name, x) are generic R operators to access the objects in the environment. See [[[] for more details. names(x) calls on the qenv object and will list all objects in the environment.

get_code() retrieves the code stored in the qenv. ... passes arguments to methods.

within() is a convenience function for evaluating inline code inside the environment of a qenv. It is a method for the base generic that wraps eval_code to provide a simplified way of passing code. within accepts only inline expressions (both simple and compound) and allows for injecting values into expr through the ... argument: as name:value pairs are passed to ..., name in expr will be replaced with value.

Subsetting

x[names] subsets objects in qenv environment and limit the code to the necessary needed to build limited objects. ... passes parameters to further methods.

Extracting dataset-specific code

When names for get_code is specified, the code returned will be limited to the lines needed to create the requested objects. The code stored in the qenv is analyzed statically to determine which lines the objects of interest depend upon. The analysis works well when objects are created with standard infix assignment operators (see ?assignOps) but it can fail in some situations.

Consider the following examples:

Case 1: Usual assignments.

q1 <-
  within(qenv(), {
    foo <- function(x) {
      x + 1
    }
    x <- 0
    y <- foo(x)
  })
get_code(q1, names = "y")

x has no dependencies, so get_code(data, names = "x") will return only the second call.
y depends on x and foo, so get_code(data, names = "y") will contain all three calls.

Case 2: Some objects are created by a function's side effects.

q2 <-
  within(qenv(){
    foo <- function() {
      x <<- x + 1
    }
    x <- 0
    foo()
    y <- x
  })
get_code(q2, names = "y")

Here, y depends on x but x is modified by foo as a side effect (not by reassignment) and so get_code(data, names = "y") will not return the foo() call.
To overcome this limitation, code dependencies can be specified manually. Lines where side effects occur can be flagged by adding "# @linksto <object name>" at the end.
Note that within evaluates code passed to expr as is and comments are ignored. In order to include comments in code one must use the eval_code function instead.

q3 <-
  eval_code(qenv(), "
    foo <- function() {
      x <<- x + 1
    }
    x <- 0
    foo() # @linksto x
    y <- x
  ")
get_code(q3, names = "y")

Now the foo() call will be properly included in the code required to recreate y.

Note that two functions that create objects as side effects, assign and data, are handled automatically.

Here are known cases where manual tagging is necessary:

  • non-standard assignment operators, e.g. %<>%

  • objects used as conditions in if statements: if (<condition>)

  • objects used to iterate over in for loops: for(i in <sequence>)

  • creating and evaluating language objects, e.g. eval(<call>)

Using language objects with within

Passing language objects to expr is generally not intended but can be achieved with do.call. Only single expressions will work and substitution is not available. See examples.

Examples

# create empty qenv
qenv()
#> <environment: 0x55f292595678> [L]
#> Parent: <environment: devtools_shims>

# evaluate code in qenv
q <- qenv()
q <- eval_code(q, "a <- 1")
q <- eval_code(q, "b <- 2L # with comment")
q <- eval_code(q, quote(library(checkmate)))
q <- eval_code(q, expression(assert_number(a)))


# Subsetting
q <- qenv()
q <- eval_code(q, "a <- 1;b<-2")
q["a"]
#> <environment: 0x55f293bdae50> [L]
#> Parent: <environment: package:checkmate>
#> Bindings:
#>  a: <dbl>
q[c("a", "b")]
#> <environment: 0x55f28f10bad0> [L]
#> Parent: <environment: package:checkmate>
#> Bindings:
#>  b: <dbl>
#>  a: <dbl>

# Extract objects from qenv
q[["a"]]
#> [1] 1
q$a
#> [1] 1

# list objects in qenv
names(q)
#> [1] "a" "b"
# retrieve code
q <- within(qenv(), {
  a <- 1
  b <- 2
})
get_code(q)
#> [1] "a <- 1\nb <- 2"
get_code(q, deparse = FALSE)
#> expression({
#> a <- 1
#> b <- 2
#> })
get_code(q, names = "a")
#> [1] "a <- 1"

q <- qenv()
q <- eval_code(q, code = c("a <- 1", "b <- 2"))
get_code(q, names = "a")
#> [1] "a <- 1"

# evaluate code using within
q <- qenv()
q <- within(q, {
  i <- iris
})
q <- within(q, {
  m <- mtcars
  f <- faithful
})
q
#> <environment: 0x55f2936133c8> [L]
#> Parent: <environment: package:checkmate>
#> Bindings:
#>  f: <df[,2]> [L]
#>  i: <df[,5]> [L]
#>  m: <df[,11]> [L]
get_code(q)
#> [1] "i <- iris\nm <- mtcars\nf <- faithful"

# inject values into code
q <- qenv()
q <- within(q, i <- iris)
within(q, print(dim(subset(i, Species == "virginica"))))
#> [1] 50  5
#> <environment: 0x55f29400d918> [L]
#> Parent: <environment: package:checkmate>
#> Bindings:
#>  i: <df[,5]> [L]
within(q, print(dim(subset(i, Species == species)))) # fails
#> <qenv.error: object 'species' not found 
#>  when evaluating qenv code:
#> print(dim(subset(i, Species == species)))>
within(q, print(dim(subset(i, Species == species))), species = "versicolor")
#> [1] 50  5
#> <environment: 0x55f28f696a08> [L]
#> Parent: <environment: package:checkmate>
#> Bindings:
#>  i: <df[,5]> [L]
species_external <- "versicolor"
within(q, print(dim(subset(i, Species == species))), species = species_external)
#> [1] 50  5
#> <environment: 0x55f290b59b48> [L]
#> Parent: <environment: package:checkmate>
#> Bindings:
#>  i: <df[,5]> [L]

# pass language objects
expr <- expression(i <- iris, m <- mtcars)
within(q, expr) # fails
#> <qenv.error: object 'expr' not found 
#>  when evaluating qenv code:
#> expr>
do.call(within, list(q, expr))
#> <environment: 0x55f292be0e38> [L]
#> Parent: <environment: package:checkmate>
#> Bindings:
#>  i: <df[,5]> [L]
#>  m: <df[,11]> [L]

exprlist <- list(expression(i <- iris), expression(m <- mtcars))
within(q, exprlist) # fails
#> <qenv.error: object 'exprlist' not found 
#>  when evaluating qenv code:
#> exprlist>
do.call(within, list(q, do.call(c, exprlist)))
#> <environment: 0x55f29359dea8> [L]
#> Parent: <environment: package:checkmate>
#> Bindings:
#>  i: <df[,5]> [L]
#>  m: <df[,11]> [L]