Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow extra sources of pronouns in f_eval() #54

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 36 additions & 3 deletions R/f-eval.R
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,24 @@
#' \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 by supplying named
#' lists or data frames in \code{...}. Each of these arguments will
#' get a pronoun based on the argument name. These names are
#' automatically prefixed with a dot. Contrarily to \code{data}, these
#' additional sources are not directly accessible, you have to
#' explicitly qualify them with the relevant pronoun to access these
#' data sources.
#'
#' @param f A one-sided formula. Any expressions wrapped in \code{ uq() } 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). \code{find_data} is a generic used to
#' find the data associated with a given object. If you want to make
#' \code{f_eval} work for your own objects, you can define a method for this
#' generic.
#' @param ... Additional sources of pronouns. These should be named
#' lists or data frames. The names of the pronouns are automatically
#' prefixed with a dot.
#' @param x An object for which you want to find associated data.
#' @export
#' @examples
Expand All @@ -40,6 +51,14 @@
#' f_eval(~ .data$cyl, mtcars)
#' f_eval(~ .env$cyl, mtcars)
#'
#' # Additional pronouns can be added by supplying named lists or data
#' # frames as argument:
#' f_eval(~ .iris$Species, iris = iris)
#'
#' # The data contained in these sources can only be accessed
#' # explicitly, through the pronoun. Direct access will fail:
#' \dontrun{f_eval(~ Species, 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?
Expand All @@ -56,15 +75,29 @@
#'
#' # Instead we need to use the prefix form of `$`.
#' f_eval(~ mean( `$`(.data, uq(var) )), mtcars)
f_eval <- function(f, data = NULL) {
f_eval <- function(f, data = NULL, ...) {
expr <- f_rhs(f_interp(f))

data <- find_data(data)
env <- environment(f)
explicit <- list(...)

if (length(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)
}
}
names(explicit) <- vapply(names(explicit),
function(x) paste0(".", x), character(1))

expr_env <- new.env(parent = env)
expr_env$.env <- complain(env)
expr_env$.data <- complain(data)
sources <- c(.env = env, .data = list(data), explicit)
for (source in names(sources)) {
expr_env[[source]] <- complain(sources[[source]])
}

eval(expr, data, expr_env)
}
Expand Down
22 changes: 21 additions & 1 deletion man/f_eval.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions tests/testthat/test-f-eval.R
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,17 @@ test_that("f_eval does quasiquoting", {
x <- 10
expect_equal(f_eval(~ uq(quote(x))), 10)
})

test_that("look into explicit pronouns", {
expect_equal(f_eval(~ .mtcars$cyl, mtcars = mtcars), mtcars$cyl)
})

test_that("data of explicit pronouns cannot be directly accessed", {
expect_error(f_eval(~ cyl, mtcars = mtcars), "not found")
})

test_that("fails when explicit is malformed", {
expect_error(f_eval(~ ., iris, mtcars), "should be named")
expect_error(f_eval(~ ., mtcars = "string"), "should contain lists")
})