diff --git a/R/f-eval.R b/R/f-eval.R index 18bb76a..dc1b69b 100644 --- a/R/f-eval.R +++ b/R/f-eval.R @@ -10,10 +10,18 @@ #' \code{.env} and \code{.data}. These are thin wrappers around \code{.data} #' and \code{.env} that throw errors if you try to access non-existent values. #' +#' You can also provide additional sources of data with the +#' \code{explicit} argument. It should be a named list containing one +#' or several lists. Each of them will get a pronoun equal to their +#' name. Contrarily to \code{data}, sources in \code{explicit} are not +#' directly accessible. Therefore you have to explicitly qualify them +#' with the relevant pronoun to access these data. +#' #' @param f A one-sided formula. Any expressions wrapped in \code{ (( )) } will #' will be "unquoted", i.e. they will be evaluated, and the results inserted #' back into the formula. See \code{\link{f_interp}} for more details. #' @param data A list (or data frame). +#' @param explicit A named list of lists (or data frames). #' @export #' @examples #' f_eval(~ 1 + 2 + 3) @@ -36,6 +44,13 @@ #' f_eval(~ .data$cyl, mtcars) #' f_eval(~ .env$cyl, mtcars) #' +#' # Additional pronouns can be added by supplying explicit: +#' f_eval(~ .iris$Species, explicit = list(.iris = iris)) +#' +#' # The data contained in these sources can only be accessed +#' # explicitly, through the pronoun. Direct access will fail: +#' \dontrun{f_eval(~ Species, explicit = list(.iris = iris))} +#' #' # Imagine you are computing the mean of a variable: #' f_eval(~ mean(cyl), mtcars) #' # How can you change the variable that's being computed? @@ -49,17 +64,29 @@ #' # take one more step of explicitness. Unfortunately data$((var)) is #' # not valid R code, so we need to use the prefix for of `$`. #' f_eval(~ mean( `$`(.data, ((var)) )), mtcars) -f_eval <- function(f, data = NULL) { +f_eval <- function(f, data = NULL, explicit = NULL) { expr <- f_rhs(f_interp(f)) if (!is.null(data) && !is.list(data)) { stop("`data` must be must be NULL, a list, or a data frame.", call. = FALSE) } + if (!is.null(explicit)) { + if (is.null(names(explicit))) { + stop("`explicit` should be named", call. = FALSE) + } + if (!all(vapply(explicit, is.list, logical(1)))) { + stop("`explicit` should contain lists or data frames", call. = FALSE) + } + } + parent_env <- environment(f) env <- new.env(parent = parent_env) - env$.env <- complain(parent_env) - env$.data <- complain(data) + + sources <- c(.env = parent_env, .data = list(data), explicit) + for (source in names(sources)) { + env[[source]] <- complain(sources[[source]]) + } eval(expr, data, env) } diff --git a/man/f_eval.Rd b/man/f_eval.Rd index 199b9f3..4619af3 100644 --- a/man/f_eval.Rd +++ b/man/f_eval.Rd @@ -4,7 +4,7 @@ \alias{f_eval} \title{Evaluate a formula} \usage{ -f_eval(f, data = NULL) +f_eval(f, data = NULL, explicit = NULL) } \arguments{ \item{f}{A one-sided formula. Any expressions wrapped in \code{ (( )) } will @@ -12,6 +12,8 @@ will be "unquoted", i.e. they will be evaluated, and the results inserted back into the formula. See \code{\link{f_interp}} for more details.} \item{data}{A list (or data frame).} + +\item{explicit}{A named list of lists (or data frames).} } \description{ \code{f_eval} evaluates the \code{\link{f_rhs}} of a formula in its environment. @@ -24,6 +26,13 @@ When used with \code{data}, \code{f_eval} provides two pronouns to make it possible to be explicit about where you want values to come from: \code{.env} and \code{.data}. These are thin wrappers around \code{.data} and \code{.env} that throw errors if you try to access non-existent values. + +You can also provide additional sources of data with the +\code{explicit} argument. It should be a named list containing one +or several lists. Each of them will get a pronoun equal to their +name. Contrarily to \code{data}, sources in \code{explicit} are not +directly accessible. Therefore you have to explicitly qualify them +with the relevant pronoun to access these data. } \examples{ f_eval(~ 1 + 2 + 3) @@ -46,6 +55,13 @@ cyl <- 10 f_eval(~ .data$cyl, mtcars) f_eval(~ .env$cyl, mtcars) +# Additional pronouns can be added by supplying explicit: +f_eval(~ .iris$Species, explicit = list(.iris = iris)) + +# The data contained in these sources can only be accessed +# explicitly, through the pronoun. Direct access will fail: +\dontrun{f_eval(~ Species, explicit = list(.iris = iris))} + # Imagine you are computing the mean of a variable: f_eval(~ mean(cyl), mtcars) # How can you change the variable that's being computed? diff --git a/tests/testthat/test-f-eval.R b/tests/testthat/test-f-eval.R index fd56df7..89b90b1 100644 --- a/tests/testthat/test-f-eval.R +++ b/tests/testthat/test-f-eval.R @@ -36,3 +36,17 @@ test_that("f_eval does quasiquoting", { x <- 10 expect_equal(f_eval(~ ((quote(x)))), 10) }) + +test_that("look into explicit pronouns", { + expect_equal(f_eval(~ .mtcars$cyl, explicit = list(.mtcars = mtcars)), mtcars$cyl) +}) + +test_that("data of explicit pronouns cannot be directly accessed", { + expect_error(f_eval(~ cyl, explicit = list(.data = mtcars))) +}) + +test_that("fails when explicit is malformed", { + expect_error(f_eval(~ ., explicit = list(mtcars)), "should be named") + expect_error(f_eval(~ ., explicit = list(pronoun = list)), "should contain lists") +}) +