diff --git a/DESCRIPTION b/DESCRIPTION index 9b465772..a60cfad7 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -53,6 +53,7 @@ Language: en-US Roxygen: list(markdown = TRUE) RoxygenNote: 7.3.2 Collate: + 'qenv-c.R' 'qenv-class.R' 'qenv-errors.R' 'qenv-concat.R' @@ -63,6 +64,7 @@ Collate: 'qenv-get_var.R' 'qenv-get_warnings.R' 'qenv-join.R' + 'qenv-length.R' 'qenv-show.R' 'qenv-within.R' 'teal.code-package.R' diff --git a/NAMESPACE b/NAMESPACE index 8ac246d6..c2673ca3 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,6 +1,13 @@ # Generated by roxygen2: do not edit by hand +S3method("$",qenv.error) S3method("[[",qenv.error) +S3method(as.list,qenv.error) +S3method(c,qenv) +S3method(c,qenv.error) +S3method(length,qenv) +S3method(length,qenv.error) +S3method(names,qenv.error) S3method(within,qenv) S3method(within,qenv.error) export(concat) diff --git a/NEWS.md b/NEWS.md index c67be508..6f70465a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,6 +4,9 @@ * `get_code()` was extended with `names` parameter and allows the code extraction to be limited to objects stored in `qenv` but limited to `names`. +* `qenv` inherits from the `environment` class, allowing to use `ls()`, `names()`, `as.environment()` and other functions on `qenv` objects. +* `join()` method is deprecated, please use `c()` instead +* `get_var()` method is deprecated, please use `get`, `[[` or `$` instead. # teal.code 0.5.0 diff --git a/R/qenv-c.R b/R/qenv-c.R new file mode 100644 index 00000000..1b9d5690 --- /dev/null +++ b/R/qenv-c.R @@ -0,0 +1,107 @@ +#' If two `qenv` can be joined +#' +#' Checks if two `qenv` objects can be combined. +#' For more information, please see [`join`] +#' @param x (`qenv`) +#' @param y (`qenv`) +#' @return `TRUE` if able to join or `character` used to print error message. +#' @keywords internal +.check_joinable <- function(x, y) { + checkmate::assert_class(x, "qenv") + checkmate::assert_class(y, "qenv") + + common_names <- intersect(rlang::env_names(x@.xData), rlang::env_names(y@.xData)) + is_overwritten <- vapply(common_names, function(el) { + !identical(get(el, x@.xData), get(el, y@.xData)) + }, logical(1)) + if (any(is_overwritten)) { + return( + paste( + "Not possible to join qenv objects if anything in their environment has been modified.\n", + "Following object(s) have been modified:\n - ", + paste(common_names[is_overwritten], collapse = "\n - ") + ) + ) + } + + shared_ids <- intersect(x@id, y@id) + if (length(shared_ids) == 0) { + return(TRUE) + } + + shared_in_x <- match(shared_ids, x@id) + shared_in_y <- match(shared_ids, y@id) + + # indices of shared ids should be 1:n in both slots + if (identical(shared_in_x, shared_in_y) && identical(shared_in_x, seq_along(shared_ids))) { + TRUE + } else if (!identical(shared_in_x, shared_in_y)) { + paste( + "The common shared code of the qenvs does not occur in the same position in both qenv objects", + "so they cannot be joined together as it's impossible to determine the evaluation's order.", + collapse = "" + ) + } else { + paste( + "There is code in the qenv objects before their common shared code", + "which means these objects cannot be joined.", + collapse = "" + ) + } +} + +#' @rdname join +#' @param ... (`qenv` or `qenv.error`). +#' @examples +#' q <- qenv() +#' q1 <- within(q, { +#' iris1 <- iris +#' mtcars1 <- mtcars +#' }) +#' q1 <- within(q1, iris2 <- iris) +#' q2 <- within(q1, mtcars2 <- mtcars) +#' qq <- c(q1, q2) +#' cat(get_code(qq)) +#' +#' @export +c.qenv <- function(...) { + dots <- rlang::list2(...) + if (!checkmate::test_list(dots[-1], types = c("qenv", "qenv.error"))) { + return(NextMethod(c, dots[[1]])) + } + + first_non_qenv_ix <- which.min(vapply(dots, inherits, what = "qenv", logical(1))) + if (first_non_qenv_ix > 1) { + return(dots[[first_non_qenv_ix]]) + } + + Reduce( + x = dots[-1], + init = dots[[1]], + f = function(x, y) { + join_validation <- .check_joinable(x, y) + + # join expressions + if (!isTRUE(join_validation)) { + stop(join_validation) + } + + id_unique <- !y@id %in% x@id + x@id <- c(x@id, y@id[id_unique]) + x@code <- c(x@code, y@code[id_unique]) + x@warnings <- c(x@warnings, y@warnings[id_unique]) + x@messages <- c(x@messages, y@messages[id_unique]) + + # insert (and overwrite) objects from y to x + x@.xData <- rlang::env_clone(x@.xData, parent = parent.env(.GlobalEnv)) + rlang::env_coalesce(env = x@.xData, from = y@.xData) + x + } + ) +} + +#' @rdname join +#' @export +c.qenv.error <- function(...) { + rlang::list2(...)[[1]] +} diff --git a/R/qenv-class.R b/R/qenv-class.R index d2a679bc..ba42b443 100644 --- a/R/qenv-class.R +++ b/R/qenv-class.R @@ -4,7 +4,7 @@ #' @name qenv-class #' @rdname qenv-class #' @slot code (`character`) representing code necessary to reproduce the environment -#' @slot env (`environment`) environment which content was generated by the evaluation +#' @slot .xData (`environment`) environment with content was generated by the evaluation #' of the `code` slot. #' @slot id (`integer`) random identifier of the code element to make sure uniqueness #' when joining. @@ -14,11 +14,60 @@ #' @exportClass qenv setClass( "qenv", - slots = c(env = "environment", code = "character", id = "integer", warnings = "character", messages = "character"), - prototype = list( - env = new.env(parent = parent.env(.GlobalEnv)), code = character(0), id = integer(0), - warnings = character(0), messages = character(0) - ) + slots = c( + code = "character", + id = "integer", + warnings = "character", + messages = "character" + ), + contains = "environment" +) + +#' It initializes the `qenv` class +#' @noRd +setMethod( + "initialize", + "qenv", + function(.Object, # nolint: object_name. + .xData, # nolint: object_name. + code = character(0L), + warnings = rep("", length(code)), + messages = rep("", length(code)), + id = integer(0L), + ...) { + # # Pre-process parameters to ensure they are ready to be used by parent constructors + stopifnot("`code` must be a character or language object." = any(is.language(code), is.character(code))) + + if (is.language(code)) { + code <- paste(lang2calls(code), collapse = "\n") + } + if (length(code)) { + code <- paste(code, collapse = "\n") + } + + if (length(id) == 0L) { + id <- sample.int(.Machine$integer.max, size = length(code)) + } + + new_xdata <- if (rlang::is_missing(.xData)) { + new.env(parent = parent.env(.GlobalEnv)) + } else { + checkmate::assert_environment(.xData) + rlang::env_clone(.xData, parent = parent.env(.GlobalEnv)) + } + lockEnvironment(new_xdata, bindings = TRUE) + + # .xData needs to be unnamed as the `.environment` constructor allows at + # most 1 unnamed formal argument of class `environment`. + # See methods::findMethods("initialize")$.environment + .Object <- methods::callNextMethod( # nolint: object_name. + # Mandatory use of `xData` to build a correct .Object@.xData + .Object, new_xdata, + code = code, messages = messages, warnings = warnings, id = id, ... + ) + + .Object + } ) #' It takes a `qenv` class and returns `TRUE` if the input is valid @@ -33,6 +82,8 @@ setValidity("qenv", function(object) { "@code and @messages slots must have the same length" } else if (any(duplicated(object@id))) { "@id contains duplicated values." + } else if (!environmentIsLocked(object@.xData)) { + "@.xData must be locked." } else { TRUE } diff --git a/R/qenv-concat.R b/R/qenv-concat.R index cc9f5ed2..c5c1bd31 100644 --- a/R/qenv-concat.R +++ b/R/qenv-concat.R @@ -38,8 +38,8 @@ setMethod("concat", signature = c("qenv", "qenv"), function(x, y) { y@messages <- c(x@messages, y@messages) # insert (and overwrite) objects from y to x - y@env <- rlang::env_clone(y@env, parent = parent.env(.GlobalEnv)) - rlang::env_coalesce(env = y@env, from = x@env) + y@.xData <- rlang::env_clone(y@.xData, parent = parent.env(.GlobalEnv)) + rlang::env_coalesce(env = y@.xData, from = x@.xData) y }) diff --git a/R/qenv-constructor.R b/R/qenv-constructor.R index 504b1259..2a10a0e8 100644 --- a/R/qenv-constructor.R +++ b/R/qenv-constructor.R @@ -12,7 +12,7 @@ #' #' @name qenv #' -#' @return Returns a `qenv` object. +#' @return `qenv` returns a `qenv` object. #' #' @seealso [`base::within()`], [`get_var()`], [`get_env()`], [`get_warnings()`], [`join()`], [`concat()`] #' @examples @@ -21,7 +21,5 @@ #' #' @export qenv <- function() { - q_env <- new.env(parent = parent.env(.GlobalEnv)) - lockEnvironment(q_env, bindings = TRUE) - methods::new("qenv", env = q_env) + methods::new("qenv") } diff --git a/R/qenv-errors.R b/R/qenv-errors.R index 4af63a56..12b0fdbd 100644 --- a/R/qenv-errors.R +++ b/R/qenv-errors.R @@ -1,2 +1,10 @@ # needed to handle try-error setOldClass("qenv.error") + +#' @export +as.list.qenv.error <- function(x, ...) { + stop(errorCondition( + list(message = conditionMessage(x)), + class = c("validation", "try-error", "simpleError") + )) +} diff --git a/R/qenv-eval_code.R b/R/qenv-eval_code.R index 25af3571..35697e54 100644 --- a/R/qenv-eval_code.R +++ b/R/qenv-eval_code.R @@ -1,7 +1,7 @@ #' Evaluate code in `qenv` #' #' @details -#' `eval_code` evaluates given code in the `qenv` environment and appends it to the `code` slot. +#' `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. #' #' @param object (`qenv`) @@ -31,7 +31,7 @@ setMethod("eval_code", signature = c("qenv", "character"), function(object, code id <- sample.int(.Machine$integer.max, size = 1) object@id <- c(object@id, id) - object@env <- rlang::env_clone(object@env, parent = parent.env(.GlobalEnv)) + object@.xData <- rlang::env_clone(object@.xData, parent = parent.env(.GlobalEnv)) code <- paste(code, collapse = "\n") object@code <- c(object@code, code) @@ -45,11 +45,11 @@ setMethod("eval_code", signature = c("qenv", "character"), function(object, code x <- withCallingHandlers( tryCatch( { - eval(single_call, envir = object@env) - if (!identical(parent.env(object@env), parent.env(.GlobalEnv))) { - # needed to make sure that @env is always a sibling of .GlobalEnv + eval(single_call, envir = object@.xData) + if (!identical(parent.env(object@.xData), parent.env(.GlobalEnv))) { + # needed to make sure that @.xData is always a sibling of .GlobalEnv # could be changed when any new package is added to search path (through library or require call) - parent.env(object@env) <- parent.env(.GlobalEnv) + parent.env(object@.xData) <- parent.env(.GlobalEnv) } NULL }, @@ -80,20 +80,19 @@ setMethod("eval_code", signature = c("qenv", "character"), function(object, code } } - object@warnings <- c(object@warnings, current_warnings) object@messages <- c(object@messages, current_messages) - lockEnvironment(object@env, bindings = TRUE) + lockEnvironment(object@.xData, bindings = TRUE) object }) setMethod("eval_code", signature = c("qenv", "language"), function(object, code) { - eval_code(object, code = paste(lang2calls(code), collapse = "\n")) + eval_code(object, code = paste(vapply(lang2calls(code), deparse1, collapse = "\n", character(1L)), collapse = "\n")) }) setMethod("eval_code", signature = c("qenv", "expression"), function(object, code) { - eval_code(object, code = paste(lang2calls(code), collapse = "\n")) + eval_code(object, code = paste(vapply(lang2calls(code), deparse1, collapse = "\n", character(1L)), collapse = "\n")) }) setMethod("eval_code", signature = c("qenv.error", "ANY"), function(object, code) { diff --git a/R/qenv-get_code.R b/R/qenv-get_code.R index cc88d633..92e64e01 100644 --- a/R/qenv-get_code.R +++ b/R/qenv-get_code.R @@ -1,7 +1,31 @@ +#' @name qenv-inheritted +#' @rdname qenv +#' +#' @details +#' +#' `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. +#' +#' @return `[[`, `$` and `get` return the value of the object named `name` in the `qenv` object. +#' @return `names` return a character vector of all the names of the objects in the `qenv` object. +#' @return `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. +#' +#' @examples +#' # Extract objects from qenv +#' q[["a"]] +#' q$a +#' +#' # list objects in qenv +#' names(q) +NULL + #' Get code from `qenv` #' #' @details -#' `get_code` retrieves the code stored in the `qenv`. `...` passes arguments to methods. +#' `get_code()` retrieves the code stored in the `qenv`. `...` passes arguments to methods. #' #' @param object (`qenv`) #' @param deparse (`logical(1)`) flag specifying whether to return code as `character` or `expression`. diff --git a/R/qenv-get_env.R b/R/qenv-get_env.R index 0d8074b9..bb37aedf 100644 --- a/R/qenv-get_env.R +++ b/R/qenv-get_env.R @@ -1,10 +1,10 @@ #' Access environment included in `qenv` #' -#' The access of environment included in `qenv@env` allows to e.g. list object names included in `qenv@env` slot. +#' The access of environment included in the `qenv` that contains all data objects. #' -#' @param object (`qenv`) +#' @param object (`qenv`). #' -#' @return An `environment` stored in `qenv@env` slot. +#' @return An `environment` stored in `qenv` slot with all data objects. #' #' @examples #' q <- qenv() @@ -13,7 +13,6 @@ #' b <- data.frame(x = 1:10) #' }) #' get_env(q1) -#' ls(get_env(q1)) #' #' @aliases get_env,qenv-method #' @aliases get_env,qenv.error-method @@ -23,10 +22,6 @@ setGeneric("get_env", function(object) { standardGeneric("get_env") }) -setMethod("get_env", "qenv", function(object) { - object@env -}) +setMethod("get_env", "qenv", function(object) object@.xData) -setMethod("get_env", "qenv.error", function(object) { - object -}) +setMethod("get_env", "qenv.error", function(object) object) diff --git a/R/qenv-get_var.R b/R/qenv-get_var.R index 152b67a4..b3959304 100644 --- a/R/qenv-get_var.R +++ b/R/qenv-get_var.R @@ -1,5 +1,9 @@ #' Get object from `qenv` #' +#' @description +#' `r lifecycle::badge("deprecated")` by native \R operators/functions: +#' `x[[name]]`, `x$name` or [get()]. +#' #' Retrieve variables from the `qenv` environment. #' #' @param object,x (`qenv`) @@ -12,7 +16,6 @@ #' q1 <- eval_code(q, code = quote(a <- 1)) #' q2 <- eval_code(q1, code = "b <- a") #' get_var(q2, "b") -#' q2[["b"]] #' #' @name get_var #' @rdname get_var @@ -27,8 +30,9 @@ setGeneric("get_var", function(object, var) { }) setMethod("get_var", signature = c("qenv", "character"), function(object, var) { + lifecycle::deprecate_soft("0.5.1", "get_var()", "base::get()") tryCatch( - get(var, envir = object@env, inherits = FALSE), + get(var, envir = object@.xData, inherits = FALSE), error = function(e) { message(conditionMessage(e)) NULL @@ -44,10 +48,6 @@ setMethod("get_var", signature = c("qenv.error", "ANY"), function(object, var) { }) #' @rdname get_var -setMethod("[[", signature = c("qenv", "ANY"), function(x, i) { - get_var(x, i) -}) - #' @export `[[.qenv.error` <- function(x, i) { stop(errorCondition( @@ -55,3 +55,21 @@ setMethod("[[", signature = c("qenv", "ANY"), function(x, i) { class = c("validation", "try-error", "simpleError") )) } + +#' @export +names.qenv.error <- function(x) NULL + +#' @export +`$.qenv.error` <- function(x, name) { + # Must allow access of elements in qenv.error object (message, call, trace, ...) + # Otherwise, it will enter an infinite recursion with the `conditionMessage(x)` call. + if (exists(name, x)) { + return(NextMethod("$", x)) + } + + class(x) <- setdiff(class(x), "qenv.error") + stop(errorCondition( + list(message = conditionMessage(x)), + class = c("validation", "try-error", "simpleError") + )) +} diff --git a/R/qenv-join.R b/R/qenv-join.R index f644223a..cccab5da 100644 --- a/R/qenv-join.R +++ b/R/qenv-join.R @@ -1,7 +1,11 @@ #' Join `qenv` objects #' +#' @description #' Checks and merges two `qenv` objects into one `qenv` object. #' +#' The `join()` function is superseded by the `c()` function. +#' +#' @details #' Any common code at the start of the `qenvs` is only placed once at the start of the joined `qenv`. #' This allows consistent behavior when joining `qenvs` which share a common ancestor. #' See below for an example. @@ -15,7 +19,7 @@ #' x <- eval_code(qenv(), expression(mtcars1 <- mtcars)) #' y <- eval_code(qenv(), expression(mtcars1 <- mtcars['wt'])) #' -#' z <- join(x, y) +#' z <- c(x, y) #' # Error message will occur #' ``` #' In this example, `mtcars1` object exists in both `x` and `y` objects but the content are not identical. @@ -40,8 +44,8 @@ #' y, #' "z <- v" #' ) -#' q <- join(x, y) -#' join_q <- join(q, z) +#' q <- c(x, y) +#' join_q <- c(q, z) #' # Error message will occur #' #' # Check the order of evaluation based on the id slot @@ -74,10 +78,10 @@ #' # Error message will occur #' #' # Check the value of temporary variable i in both objects -#' x@env$i # Output: 2 -#' y@env$i # Output: 3 +#' x$i # Output: 2 +#' y$i # Output: 3 #' ``` -#' `join()` fails to provide a proper result because of the temporary variable `i` exists +#' `c()` fails to provide a proper result because of the temporary variable `i` exists #' in both objects but has different value. #' To fix this, we can set `i <- NULL` in the code expression for both objects. #' ```r @@ -100,7 +104,7 @@ #' # dummy i variable to fix it #' i <- NULL" #' ) -#' q <- join(x,y) +#' q <- c(x,y) #' ``` #' #' @param x (`qenv`) @@ -115,14 +119,14 @@ #' q1 <- eval_code(q1, "iris2 <- iris") #' q2 <- eval_code(q2, "mtcars2 <- mtcars") #' qq <- join(q1, q2) -#' get_code(qq) +#' cat(get_code(qq)) #' #' common_q <- eval_code(q, quote(x <- 1)) #' y_q <- eval_code(common_q, quote(y <- x * 2)) #' z_q <- eval_code(common_q, quote(z <- x * 3)) #' join_q <- join(y_q, z_q) #' # get_code only has "x <- 1" occurring once -#' get_code(join_q) +#' cat(get_code(join_q)) #' #' @include qenv-errors.R #' @@ -136,81 +140,16 @@ setGeneric("join", function(x, y) standardGeneric("join")) setMethod("join", signature = c("qenv", "qenv"), function(x, y) { - join_validation <- .check_joinable(x, y) - - # join expressions - if (!isTRUE(join_validation)) { - stop(join_validation) - } - - id_unique <- !y@id %in% x@id - x@id <- c(x@id, y@id[id_unique]) - x@code <- c(x@code, y@code[id_unique]) - x@warnings <- c(x@warnings, y@warnings[id_unique]) - x@messages <- c(x@messages, y@messages[id_unique]) - - # insert (and overwrite) objects from y to x - x@env <- rlang::env_clone(x@env, parent = parent.env(.GlobalEnv)) - rlang::env_coalesce(env = x@env, from = y@env) - x + lifecycle::deprecate_soft("0.5.1", "join()", "c()") + c(x, y) }) setMethod("join", signature = c("qenv", "qenv.error"), function(x, y) { + lifecycle::deprecate_soft("0.5.1", "join()", "c()") y }) setMethod("join", signature = c("qenv.error", "ANY"), function(x, y) { + lifecycle::deprecate_soft("0.5.1", "join()", "c()") x }) - -#' If two `qenv` can be joined -#' -#' Checks if two `qenv` objects can be combined. -#' For more information, please see [`join`] -#' @param x (`qenv`) -#' @param y (`qenv`) -#' @return `TRUE` if able to join or `character` used to print error message. -#' @keywords internal -.check_joinable <- function(x, y) { - checkmate::assert_class(x, "qenv") - checkmate::assert_class(y, "qenv") - - common_names <- intersect(rlang::env_names(x@env), rlang::env_names(y@env)) - is_overwritten <- vapply(common_names, function(el) { - !identical(get(el, x@env), get(el, y@env)) - }, logical(1)) - if (any(is_overwritten)) { - return( - paste( - "Not possible to join qenv objects if anything in their environment has been modified.\n", - "Following object(s) have been modified:\n - ", - paste(common_names[is_overwritten], collapse = "\n - ") - ) - ) - } - - shared_ids <- intersect(x@id, y@id) - if (length(shared_ids) == 0) { - return(TRUE) - } - - shared_in_x <- match(shared_ids, x@id) - shared_in_y <- match(shared_ids, y@id) - - # indices of shared ids should be 1:n in both slots - if (identical(shared_in_x, shared_in_y) && identical(shared_in_x, seq_along(shared_ids))) { - TRUE - } else if (!identical(shared_in_x, shared_in_y)) { - paste( - "The common shared code of the qenvs does not occur in the same position in both qenv objects", - "so they cannot be joined together as it's impossible to determine the evaluation's order.", - collapse = "" - ) - } else { - paste( - "There is code in the qenv objects before their common shared code", - "which means these objects cannot be joined.", - collapse = "" - ) - } -} diff --git a/R/qenv-length.R b/R/qenv-length.R new file mode 100644 index 00000000..f94e9d88 --- /dev/null +++ b/R/qenv-length.R @@ -0,0 +1,5 @@ +#' @export +length.qenv <- function(x) length(x@.xData) + +#' @export +length.qenv.error <- function(x) 0 diff --git a/R/qenv-show.R b/R/qenv-show.R index a2576f38..b2fa7447 100644 --- a/R/qenv-show.R +++ b/R/qenv-show.R @@ -16,5 +16,5 @@ #' @importFrom methods show #' @export setMethod("show", "qenv", function(object) { - rlang::env_print(object@env) + rlang::env_print(object@.xData) }) diff --git a/R/qenv-within.R b/R/qenv-within.R index b38a641e..7d5fd32d 100644 --- a/R/qenv-within.R +++ b/R/qenv-within.R @@ -1,7 +1,7 @@ #' Evaluate Expression in `qenv` #' #' @details -#' `within` is a convenience function for evaluating inline code inside the environment of a `qenv`. +#' `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: diff --git a/README.md b/README.md index 27c7921f..ea58b7aa 100644 --- a/README.md +++ b/README.md @@ -62,9 +62,9 @@ my_qenv #> Parent: #> Bindings: #> • x: [L] -get_env(my_qenv) +as.environment(my_qenv) #> -ls(get_env(my_qenv)) +names(my_qenv) #> [1] "x" ``` @@ -77,9 +77,9 @@ qenv_2 #> • x: [L] #> • y: [L] #> • z: [L] -get_env(qenv_2) +environment(qenv_2) #> -ls(get_env(qenv_2)) +names(qenv_2) #> [1] "x" "y" "z" ``` diff --git a/inst/WORDLIST b/inst/WORDLIST index 44b9561f..4cce4b1d 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -1,7 +1,7 @@ Forkers -Hoffmann -Reproducibility funder +Hoffmann qenv repo +Reproducibility reproducibility diff --git a/man/dot-check_joinable.Rd b/man/dot-check_joinable.Rd index f0ef8684..e7f55478 100644 --- a/man/dot-check_joinable.Rd +++ b/man/dot-check_joinable.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/qenv-join.R +% Please edit documentation in R/qenv-c.R \name{.check_joinable} \alias{.check_joinable} \title{If two \code{qenv} can be joined} diff --git a/man/get_env.Rd b/man/get_env.Rd index b6e86a34..d2333f8d 100644 --- a/man/get_env.Rd +++ b/man/get_env.Rd @@ -9,13 +9,13 @@ get_env(object) } \arguments{ -\item{object}{(\code{qenv})} +\item{object}{(\code{qenv}).} } \value{ -An \code{environment} stored in \code{qenv@env} slot. +An \code{environment} stored in \code{qenv} slot with all data objects. } \description{ -The access of environment included in \code{qenv@env} allows to e.g. list object names included in \code{qenv@env} slot. +The access of environment included in the \code{qenv} that contains all data objects. } \examples{ q <- qenv() @@ -24,6 +24,5 @@ q1 <- within(q, { b <- data.frame(x = 1:10) }) get_env(q1) -ls(get_env(q1)) } diff --git a/man/get_var.Rd b/man/get_var.Rd index 06aee7d8..7b40bbaa 100644 --- a/man/get_var.Rd +++ b/man/get_var.Rd @@ -4,12 +4,12 @@ \alias{get_var} \alias{get_var,qenv,character-method} \alias{get_var,qenv.error,ANY-method} -\alias{[[,qenv-method} +\alias{[[.qenv.error} \title{Get object from \code{qenv}} \usage{ get_var(object, var) -\S4method{[[}{qenv}(x, i) +\method{[[}{qenv.error}(x, i) } \arguments{ \item{object, x}{(\code{qenv})} @@ -20,6 +20,9 @@ get_var(object, var) The value of required variable (\code{var}) within \code{qenv} object. } \description{ +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} by native \R operators/functions: +\code{x[[name]]}, \code{x$name} or \code{\link[=get]{get()}}. + Retrieve variables from the \code{qenv} environment. } \examples{ @@ -27,6 +30,5 @@ q <- qenv() q1 <- eval_code(q, code = quote(a <- 1)) q2 <- eval_code(q1, code = "b <- a") get_var(q2, "b") -q2[["b"]] } diff --git a/man/join.Rd b/man/join.Rd index 40f60d0c..30d344fd 100644 --- a/man/join.Rd +++ b/man/join.Rd @@ -1,15 +1,23 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/qenv-join.R -\name{join} +% Please edit documentation in R/qenv-c.R, R/qenv-join.R +\name{c.qenv} +\alias{c.qenv} +\alias{c.qenv.error} \alias{join} \alias{join,qenv,qenv-method} \alias{join,qenv,qenv.error-method} \alias{join,qenv.error,ANY-method} \title{Join \code{qenv} objects} \usage{ +\method{c}{qenv}(...) + +\method{c}{qenv.error}(...) + join(x, y) } \arguments{ +\item{...}{(\code{qenv} or \code{qenv.error}).} + \item{x}{(\code{qenv})} \item{y}{(\code{qenv})} @@ -19,6 +27,8 @@ join(x, y) } \description{ Checks and merges two \code{qenv} objects into one \code{qenv} object. + +The \code{join()} function is superseded by the \code{c()} function. } \details{ Any common code at the start of the \code{qenvs} is only placed once at the start of the joined \code{qenv}. @@ -34,7 +44,7 @@ Example: \if{html}{\out{
}}\preformatted{x <- eval_code(qenv(), expression(mtcars1 <- mtcars)) y <- eval_code(qenv(), expression(mtcars1 <- mtcars['wt'])) -z <- join(x, y) +z <- c(x, y) # Error message will occur }\if{html}{\out{
}} @@ -59,8 +69,8 @@ z <- eval_code( y, "z <- v" ) -q <- join(x, y) -join_q <- join(q, z) +q <- c(x, y) +join_q <- c(q, z) # Error message will occur # Check the order of evaluation based on the id slot @@ -93,11 +103,11 @@ q <- join(x,y) # Error message will occur # Check the value of temporary variable i in both objects -x@env$i # Output: 2 -y@env$i # Output: 3 +x$i # Output: 2 +y$i # Output: 3 }\if{html}{\out{}} -\code{join()} fails to provide a proper result because of the temporary variable \code{i} exists +\code{c()} fails to provide a proper result because of the temporary variable \code{i} exists in both objects but has different value. To fix this, we can set \code{i <- NULL} in the code expression for both objects. @@ -120,24 +130,34 @@ y <- eval_code( # dummy i variable to fix it i <- NULL" ) -q <- join(x,y) +q <- c(x,y) }\if{html}{\out{}} } } \examples{ +q <- qenv() +q1 <- within(q, { + iris1 <- iris + mtcars1 <- mtcars +}) +q1 <- within(q1, iris2 <- iris) +q2 <- within(q1, mtcars2 <- mtcars) +qq <- c(q1, q2) +cat(get_code(qq)) + q <- qenv() q1 <- eval_code(q, expression(iris1 <- iris, mtcars1 <- mtcars)) q2 <- q1 q1 <- eval_code(q1, "iris2 <- iris") q2 <- eval_code(q2, "mtcars2 <- mtcars") qq <- join(q1, q2) -get_code(qq) +cat(get_code(qq)) common_q <- eval_code(q, quote(x <- 1)) y_q <- eval_code(common_q, quote(y <- x * 2)) z_q <- eval_code(common_q, quote(z <- x * 3)) join_q <- join(y_q, z_q) # get_code only has "x <- 1" occurring once -get_code(join_q) +cat(get_code(join_q)) } diff --git a/man/qenv-class.Rd b/man/qenv-class.Rd index acb66ffe..4470c2e8 100644 --- a/man/qenv-class.Rd +++ b/man/qenv-class.Rd @@ -12,7 +12,7 @@ Reproducible class with environment and code. \describe{ \item{\code{code}}{(\code{character}) representing code necessary to reproduce the environment} -\item{\code{env}}{(\code{environment}) environment which content was generated by the evaluation +\item{\code{.xData}}{(\code{environment}) environment with content was generated by the evaluation of the \code{code} slot.} \item{\code{id}}{(\code{integer}) random identifier of the code element to make sure uniqueness diff --git a/man/qenv.Rd b/man/qenv.Rd index 232c5506..6204d617 100644 --- a/man/qenv.Rd +++ b/man/qenv.Rd @@ -8,6 +8,7 @@ \alias{eval_code,qenv,language-method} \alias{eval_code,qenv,expression-method} \alias{eval_code,qenv.error,ANY-method} +\alias{qenv-inheritted} \alias{get_code} \alias{get_code,qenv-method} \alias{get_code,qenv.error-method} @@ -39,10 +40,18 @@ For more details see the "Extracting dataset-specific code" section.} \item{expr}{(\code{expression}) to evaluate. Must be inline code, see \verb{Using language objects...}} } \value{ -Returns a \code{qenv} object. +\code{qenv} returns a \code{qenv} object. \code{eval_code} returns a \code{qenv} object with \code{expr} evaluated or \code{qenv.error} if evaluation fails. +\code{[[}, \code{$} and \code{get} return the value of the object named \code{name} in the \code{qenv} object. + +\code{names} return a character vector of all the names of the objects in the \code{qenv} object. + +\code{ls} return a character vector of the names of the objects in the \code{qenv} object. +It will only show the objects that are not named with a dot prefix, unless +the \code{all.names = TRUE}, which will show all objects. + \code{get_code} returns the traced code (from \verb{@code} slot) in the form specified by \code{deparse}. \code{within} returns a \code{qenv} object with \code{expr} evaluated or \code{qenv.error} if evaluation fails. @@ -56,12 +65,16 @@ Create a \code{qenv} object and evaluate code in it to track code history. \code{qenv()} instantiates a \code{qenv} with an empty environment. Any changes must be made by evaluating code in it with \code{eval_code} or \code{within}, thereby ensuring reproducibility. -\code{eval_code} evaluates given code in the \code{qenv} environment and appends it to the \code{code} slot. +\code{eval_code()} evaluates given code in the \code{qenv} environment and appends it to the \code{code} slot. Thus, if the \code{qenv} had been instantiated empty, contents of the environment are always a result of the stored code. -\code{get_code} retrieves the code stored in the \code{qenv}. \code{...} passes arguments to methods. +\code{x[[name]]}, \code{x$name} and \code{get(name, x)} are generic \R operators to access the objects in the environment. +See [\code{[[}] for more details. +\code{names(x)} calls on the \code{qenv} object and will list all objects in the environment. -\code{within} is a convenience function for evaluating inline code inside the environment of a \code{qenv}. +\code{get_code()} retrieves the code stored in the \code{qenv}. \code{...} passes arguments to methods. + +\code{within()} is a convenience function for evaluating inline code inside the environment of a \code{qenv}. It is a method for the \code{base} generic that wraps \code{eval_code} to provide a simplified way of passing code. \code{within} accepts only inline expressions (both simple and compound) and allows for injecting values into \code{expr} through the \code{...} argument: @@ -154,6 +167,12 @@ q <- eval_code(q, "a <- 1") q <- eval_code(q, quote(library(checkmate))) q <- eval_code(q, expression(assert_number(a))) +# Extract objects from qenv +q[["a"]] +q$a + +# list objects in qenv +names(q) # retrieve code q <- within(qenv(), { a <- 1 diff --git a/tests/testthat/test-qenv-class.R b/tests/testthat/test-qenv-class.R new file mode 100644 index 00000000..083d37e0 --- /dev/null +++ b/tests/testthat/test-qenv-class.R @@ -0,0 +1,27 @@ +testthat::describe("methods::new(qenv)", { + testthat::it("creates a locked environment", { + expect_true(environmentIsLocked(as.environment(methods::new("qenv")))) + }) + + testthat::it("creates a locked environment when .xData is manually defined", { + new_env <- new.env() + expect_false(environmentIsLocked(new_env)) + + expect_true(environmentIsLocked(as.environment(methods::new("qenv", .xData = new_env)))) + }) + + testthat::it("throws error when id and code length doesn't match", { + expect_error(methods::new("qenv", id = 1L), "@code and @id slots must have the same length\\.") + }) + + testthat::it("throws error when .xData is not an environment", { + expect_error(methods::new("qenv", .xData = 2), "Must be an environment, not 'double'\\.") + }) + + testthat::it("throws error when code is not language or character object", { + expect_error(methods::new("qenv", code = 2), "`code` must be a character or language object\\.") + }) + testthat::it("initialized qenv(s) have different environments", { + testthat::expect_false(identical(qenv()@.xData, qenv()@.xData)) + }) +}) diff --git a/tests/testthat/test-qenv_concat.R b/tests/testthat/test-qenv_concat.R index c7b55739..c4d5bf6a 100644 --- a/tests/testthat/test-qenv_concat.R +++ b/tests/testthat/test-qenv_concat.R @@ -5,7 +5,7 @@ testthat::test_that("Concatenate two identical qenvs outputs", { q12 <- concat(q1, q2) - testthat::expect_equal(q12@env, q1@env) + testthat::expect_equal(q12@.xData, q1@.xData) testthat::expect_identical( q12@code, c("iris1 <- iris", "iris1 <- iris") @@ -21,7 +21,7 @@ testthat::test_that("Concatenate two independent qenvs results in object having q12 <- concat(q1, q2) - testthat::expect_equal(q12@env, list2env(list(iris1 = iris, mtcars1 = mtcars))) + testthat::expect_equal(q12@.xData, list2env(list(iris1 = iris, mtcars1 = mtcars))) testthat::expect_identical( q12@code, c("iris1 <- iris", "mtcars1 <- mtcars") diff --git a/tests/testthat/test-qenv_constructor.R b/tests/testthat/test-qenv_constructor.R index 20bec3dc..b0d3db92 100644 --- a/tests/testthat/test-qenv_constructor.R +++ b/tests/testthat/test-qenv_constructor.R @@ -1,19 +1,70 @@ +testthat::describe("qenv inherits from environment: ", { + testthat::it("is an environment", { + testthat::expect_true(is.environment(qenv())) + }) + + testthat::it("names() shows nothing on empty environment", { + testthat::expect_identical(names(qenv()), character(0)) + }) + + testthat::it("names() shows available objets", { + q <- within(qenv(), iris <- iris) + testthat::expect_setequal(names(q), "iris") + }) + + testthat::it("names() shows hidden objects", { + q <- within(qenv(), { + iris <- iris + .hidden <- 2 + }) + testthat::expect_setequal(names(q), c("iris", ".hidden")) + }) + + testthat::it("ls() does not show hidden objects", { + q <- within(qenv(), { + iris <- iris + .hidden <- 2 + }) + testthat::expect_setequal(ls(q), c("iris")) + }) + + testthat::it("ls(all.names = TRUE) show all objects", { + q <- eval_code(qenv(), " + iris <- iris + .hidden <- 2 + ") + testthat::expect_setequal(ls(q, all.names = TRUE), c("iris", ".hidden")) + }) + + testthat::it("does not allow binding to be added", { + q <- qenv() + testthat::expect_error(q$x <- 1, "cannot add bindings to a locked environment") + }) + + testthat::it("does not allow binding to be modified", { + q <- within(qenv(), obj <- 1) + testthat::expect_error(q$obj <- 2, "cannot change value of locked binding for 'obj'") + }) +}) + testthat::test_that("constructor returns qenv", { q <- qenv() testthat::expect_s4_class(q, "qenv") - testthat::expect_identical(ls(q@env), character(0)) + testthat::expect_identical(names(q), character(0)) testthat::expect_identical(q@code, character(0)) testthat::expect_identical(q@id, integer(0)) testthat::expect_identical(q@warnings, character(0)) testthat::expect_identical(q@messages, character(0)) }) -testthat::test_that("parent of qenv environment is the parent of .GlobalEnv", { - q <- qenv() - testthat::expect_identical(parent.env(q@env), parent.env(.GlobalEnv)) -}) +testthat::describe("parent of qenv environment is the parent of .GlobalEnv", { + testthat::it("via slot", { + q <- qenv() + testthat::expect_identical(parent.env(q@.xData), parent.env(.GlobalEnv)) + }) -testthat::test_that("parent of qenv environment is locked", { - q <- qenv() - testthat::expect_error(q@env$x <- 1, "cannot add bindings to a locked environment") + testthat::it("via qenv directly", { + q <- qenv() + testthat::expect_identical(parent.env(q), parent.env(.GlobalEnv)) + }) }) diff --git a/tests/testthat/test-qenv_eval_code.R b/tests/testthat/test-qenv_eval_code.R index 12a2a7a0..6621615e 100644 --- a/tests/testthat/test-qenv_eval_code.R +++ b/tests/testthat/test-qenv_eval_code.R @@ -2,7 +2,7 @@ testthat::test_that("eval_code evaluates the code in the qenvs environment", { q <- qenv() q1 <- eval_code(q, quote(iris1 <- iris)) q2 <- eval_code(q1, quote(b <- nrow(iris1))) - testthat::expect_identical(get_var(q2, "b"), 150L) + testthat::expect_identical(q2$b, 150L) }) testthat::test_that("eval_code doesn't have access to environment where it's called", { @@ -15,16 +15,6 @@ testthat::test_that("eval_code doesn't have access to environment where it's cal ) }) -testthat::test_that("@env in qenv is always a sibling of .GlobalEnv", { - q1 <- qenv() - testthat::expect_identical(parent.env(q1@env), parent.env(.GlobalEnv)) - - q2 <- eval_code(q1, quote(a <- 1L)) - testthat::expect_identical(parent.env(q2@env), parent.env(.GlobalEnv)) - q3 <- eval_code(q2, quote(b <- 2L)) - testthat::expect_identical(parent.env(q3@env), parent.env(.GlobalEnv)) -}) - testthat::test_that("getting object from the package namespace works even if library in the same call", { testthat::expect_s4_class( eval_code( @@ -42,21 +32,21 @@ testthat::test_that("eval_code works with character", { q1 <- eval_code(qenv(), "a <- 1") testthat::expect_identical(q1@code, "a <- 1") - testthat::expect_equal(q1@env, list2env(list(a = 1))) + testthat::expect_equal(q1@.xData, list2env(list(a = 1))) }) testthat::test_that("eval_code works with expression", { q1 <- eval_code(qenv(), as.expression(quote(a <- 1))) testthat::expect_identical(q1@code, "a <- 1") - testthat::expect_equal(q1@env, list2env(list(a = 1))) + testthat::expect_equal(q1@.xData, list2env(list(a = 1))) }) testthat::test_that("eval_code works with quoted", { q1 <- eval_code(qenv(), quote(a <- 1)) testthat::expect_identical(q1@code, "a <- 1") - testthat::expect_equal(q1@env, list2env(list(a = 1))) + testthat::expect_equal(q1@.xData, list2env(list(a = 1))) }) testthat::test_that("eval_code works with quoted code block", { @@ -72,11 +62,15 @@ testthat::test_that("eval_code works with quoted code block", { q1@code, "a <- 1\nb <- 2" ) - testthat::expect_equal(q1@env, list2env(list(a = 1, b = 2))) + testthat::expect_equal(q1@.xData, list2env(list(a = 1, b = 2))) }) testthat::test_that("eval_code fails with unquoted expression", { - testthat::expect_error(eval_code(qenv(), a <- b), "object 'b' not found") + b <- 3 + testthat::expect_error( + eval_code(qenv(), a <- b), + "unable to find an inherited method for function .eval_code. for signature" + ) }) testthat::test_that("an error when calling eval_code returns a qenv.error object which has message and trace", { diff --git a/tests/testthat/test-qenv_get_code.R b/tests/testthat/test-qenv_get_code.R index 0f9aedf9..fe908570 100644 --- a/tests/testthat/test-qenv_get_code.R +++ b/tests/testthat/test-qenv_get_code.R @@ -120,7 +120,7 @@ testthat::test_that("extracts the code without downstream usage", { ) }) -testthat::test_that("works for datanames of length > 1", { +testthat::test_that("works for names of length > 1", { code <- c( "a <- 1", "b <- 2" diff --git a/tests/testthat/test-qenv_get_var.R b/tests/testthat/test-qenv_get_var.R index 8fbf0484..c922e699 100644 --- a/tests/testthat/test-qenv_get_var.R +++ b/tests/testthat/test-qenv_get_var.R @@ -1,20 +1,23 @@ -testthat::test_that("get_var and `[[`return error if object is qenv.error", { +testthat::test_that("get_var, `$` and `[[` return error if object is qenv.error", { q <- eval_code(qenv(), quote(x <- 1)) q <- eval_code(q, quote(y <- w * x)) testthat::expect_error(get_var(q, "x"), "when evaluating qenv code") testthat::expect_error(q[["x"]], "when evaluating qenv code") + testthat::expect_error(q$x, "when evaluating qenv code") }) -testthat::test_that("get_var and `[[` return object from qenv environment", { +testthat::test_that("get_var, `$` and `[[` return object from qenv environment", { q <- eval_code(qenv(), quote(x <- 1)) q <- eval_code(q, quote(y <- 5 * x)) testthat::expect_equal(get_var(q, "y"), 5) testthat::expect_equal(q[["x"]], 1) + testthat::expect_equal(q$x, 1) }) -testthat::test_that("get_var and `[[` return NULL if object not in qenv environment", { + +testthat::test_that("get_var, `$` and `[[` return NULL if object not in qenv environment", { q <- eval_code(qenv(), quote(x <- 1)) q <- eval_code(q, quote(y <- 5 * x)) @@ -22,12 +25,31 @@ testthat::test_that("get_var and `[[` return NULL if object not in qenv environm testthat::expect_message(get_var(q, "z"), "object 'z' not found") testthat::expect_null(q[["w"]]) - testthat::expect_message(q[["w"]], "object 'w' not found") + testthat::expect_null(q$w) }) -testthat::test_that("get_var and `[[` only returns objects from qenv, not parent environment(s)", { +testthat::test_that("get_var, `$` and `[[` only returns objects from qenv, not parent environment(s)", { q <- qenv() + new_env <- new.env(parent = parent.env(q)) + new_env$an_object <- 2 + + testthat::expect_null(get_var(q, "an_object")) + testthat::expect_null(q[["an_object"]]) + testthat::expect_null(q$an_object) +}) + +testthat::test_that("get_var, `$` and `[[` only returns objects from qenv, not .GlobalEnv", { + if (is.null(.GlobalEnv)) { + withr::defer(rm("an_object", envir = .GlobalEnv)) + } else { + old_object <- .GlobalEnv$an_object + withr::defer(.GlobalEnv$an_object <- old_object) # nolint: object_name. + } + .GlobalEnv$an_object <- iris # nolint: object_name. + + q <- qenv() testthat::expect_null(get_var(q, "iris")) testthat::expect_null(q[["iris"]]) + testthat::expect_null(q$iris) }) diff --git a/tests/testthat/test-qenv_join.R b/tests/testthat/test-qenv_join.R index fdc218d8..df40bc71 100644 --- a/tests/testthat/test-qenv_join.R +++ b/tests/testthat/test-qenv_join.R @@ -3,9 +3,9 @@ testthat::test_that("Joining two identical qenvs outputs the same object", { q2 <- q1 testthat::expect_true(.check_joinable(q1, q2)) - q <- join(q1, q2) + q <- c(q1, q2) - testthat::expect_equal(q@env, q1@env) + testthat::expect_equal(q@.xData, q1@.xData) testthat::expect_identical(q@code, "iris1 <- iris") testthat::expect_identical(q@id, q1@id) }) @@ -15,9 +15,9 @@ testthat::test_that("Joining two independent qenvs results in object having comb q2 <- eval_code(qenv(), quote(mtcars1 <- mtcars)) testthat::expect_true(.check_joinable(q1, q2)) - q <- join(q1, q2) + q <- c(q1, q2) - testthat::expect_equal(q@env, list2env(list(iris1 = iris, mtcars1 = mtcars))) + testthat::expect_equal(q@.xData, list2env(list(iris1 = iris, mtcars1 = mtcars))) testthat::expect_identical( q@code, c("iris1 <- iris", "mtcars1 <- mtcars") @@ -36,7 +36,7 @@ testthat::test_that("Joined qenv does not duplicate common code", { q2 <- eval_code(q2, quote(mtcars2 <- mtcars)) testthat::expect_true(.check_joinable(q1, q2)) - q <- join(q1, q2) + q <- c(q1, q2) testthat::expect_identical( q@code, @@ -50,7 +50,7 @@ testthat::test_that("Not able to join two qenvs if any of the shared objects cha q2 <- eval_code(qenv(), quote(iris1 <- head(iris))) testthat::expect_match(.check_joinable(q1, q2), "Not possible to join qenv objects") - testthat::expect_error(join(q1, q2), "Not possible to join qenv objects") + testthat::expect_error(c(q1, q2), "Not possible to join qenv objects") }) testthat::test_that("join does not duplicate code but adds only extra code", { @@ -60,7 +60,7 @@ testthat::test_that("join does not duplicate code but adds only extra code", { q2 <- eval_code(q2, quote(mtcars2 <- mtcars)) testthat::expect_true(.check_joinable(q1, q2)) - q <- join(q1, q2) + q <- c(q1, q2) testthat::expect_identical( q@code, @@ -68,8 +68,8 @@ testthat::test_that("join does not duplicate code but adds only extra code", { ) testthat::expect_equal( - as.list(q@env), - list(iris1 = iris, iris2 = iris, mtcars1 = mtcars, mtcars2 = mtcars) + q@.xData, + list2env(list(iris1 = iris, iris2 = iris, mtcars1 = mtcars, mtcars2 = mtcars)) ) testthat::expect_identical(q@id, c(q1@id, q2@id[2])) @@ -85,7 +85,7 @@ testthat::test_that("Not possible to join qenvs which share some code when one o q1 <- eval_code(q1, quote(iris2 <- iris)) q2 <- eval_code(q2, quote(mtcars1 <- head(mtcars))) - testthat::expect_error(join(q1, q2)) + testthat::expect_error(c(q1, q2)) }) testthat::test_that("qenv objects are mergeable if they don't share any code (identified by id)", { @@ -93,9 +93,9 @@ testthat::test_that("qenv objects are mergeable if they don't share any code (id q2 <- eval_code(qenv(), code = quote(a1 <- 1)) testthat::expect_true(.check_joinable(q1, q2)) - cq <- join(q1, q2) + cq <- c(q1, q2) testthat::expect_s4_class(cq, "qenv") - testthat::expect_equal(cq@env, list2env(list(a1 = 1))) + testthat::expect_equal(cq@.xData, list2env(list(a1 = 1))) testthat::expect_identical(cq@code, c("a1 <- 1", "a1 <- 1")) testthat::expect_identical(cq@id, c(q1@id, q2@id)) }) @@ -108,7 +108,7 @@ testthat::test_that("qenv objects are mergeable if they share common initial qen cq <- join(q1, q2) testthat::expect_s4_class(cq, "qenv") - testthat::expect_equal(cq@env, list2env(list(a1 = 1, b1 = 2, a2 = 3))) + testthat::expect_equal(cq@.xData, list2env(list(a1 = 1, b1 = 2, a2 = 3))) testthat::expect_identical( cq@code, c("a1 <- 1", "a2 <- 3", "b1 <- 2") diff --git a/tests/testthat/test-qenv_within.R b/tests/testthat/test-qenv_within.R index 6073d80b..b72e5265 100644 --- a/tests/testthat/test-qenv_within.R +++ b/tests/testthat/test-qenv_within.R @@ -14,6 +14,16 @@ testthat::test_that("simple and compound expressions are evaluated", { ) }) +testthat::test_that("multiline expressions are evaluated", { + q <- qenv() + testthat::expect_no_error( + within(q, a <- function(x) { + y <- x + 1 + y + 3 + }) + ) +}) + # code identity ---- testthat::test_that("styling of input code does not impact evaluation results", { q <- qenv() @@ -54,12 +64,12 @@ testthat::test_that("styling of input code does not impact evaluation results", # return value ---- -testthat::test_that("within.qenv renturns a `qenv` where `@env` is a deep copy of that in `data`", { +testthat::test_that("within.qenv renturns a `qenv` where `@.xData` is a deep copy of that in `data`", { q <- qenv() q <- within(qenv(), i <- iris) qq <- within(q, {}) - testthat::expect_equal(q@env, qq@env) - testthat::expect_false(identical(q@env, qq@env)) + testthat::expect_equal(q@.xData, qq@.xData) + testthat::expect_false(identical(q@.xData, qq@.xData)) }) testthat::test_that("within.qenv renturns qenv.error even if evaluation raises error", { diff --git a/vignettes/qenv.Rmd b/vignettes/qenv.Rmd index 00fbf4ba..33b73a01 100644 --- a/vignettes/qenv.Rmd +++ b/vignettes/qenv.Rmd @@ -32,7 +32,7 @@ The `eval_code()` function executes code within a `qenv` environment, yielding a # evaluate code in qenv my_qenv <- eval_code(empty_qenv, "x <- 2") print(my_qenv) -get_env(my_qenv) +as.environment(my_qenv) q1 <- eval_code(my_qenv, "y <- x * 2") @@ -40,11 +40,11 @@ q1 <- eval_code(q1, "z <- y * 2") # my_qenv still contains only x print(my_qenv) -ls(get_env(my_qenv)) +names(my_qenv) # q1 contains x, y and z print(q1) -ls(get_env(q1)) +names(q1) ``` The same result can be achieved with the `within` method for the `qenv` class. @@ -112,10 +112,10 @@ common_q <- eval_code(qenv(), quote(x <- 1)) x_q <- eval_code(common_q, quote(y <- 5)) y_q <- eval_code(common_q, quote(z <- 5)) -join_q <- join(x_q, y_q) +join_q <- c(x_q, y_q) print(join_q) -ls(get_env(join_q)) +names(join_q) ``` The feasibility of joining `qenv` objects hinges on the contents of the environments and the code's order. Refer to the function documentation for further details.