Skip to content

Instantly share code, notes, and snippets.

@JonasMoss
Last active April 25, 2018 13:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JonasMoss/43a80eca0ee32bc04ec62b7e3e4bfa0c to your computer and use it in GitHub Desktop.
Save JonasMoss/43a80eca0ee32bc04ec62b7e3e4bfa0c to your computer and use it in GitHub Desktop.
A function that evaluates a call as if it was defined in a specified environment.
#' Evaluates a call as if its function was defined in a specified environment
#'
#' When a name is encountered in the definition of a function, the search path
#' for that name is given by the defining environment of the function. This is
#' good behaviour, since it allows simple reasoning about how a function should
#' behave: If two calls to a function defined in a constant environment \code{e}
#' yield different results, this must be because they are given different
#' arguments.
#'
#' Sometimes, a function is defined to make messy code more readable, but is
#' only intended to be used once. In these cases, you might want to call the
#' function as if it was defined in the body of the work you're currently
#' working with. That is what this function does for you.
#'
#' @param call The function call to evaluate.
#' @param env The environment where the function is defined. Defaults to
#' \code{parent.frame}, which makes the defining environment equal to the
#' current environment.
#' @param quote Logical; If \code{TRUE}, substitutes the argument.
#' @return The value of the function call when evaluated in the specified
#' environment.
#' @examples
#' x = 3
#' f = function() x^2
#' g = function() {
#' x = 10
#' S(f())
#' }
#' g() # Evalutes to 100
#' f() # Evalutes to 9
#' S(f()) # Evaluates 9
S = function(call, env = parent.frame(), quote = TRUE) {
call = if(quote) substitute(call) else call
fn = call[[1]] # Name of the function as a 'name' object.
args = as.list(call[-1]) # A list of unevaluated arguments.
# I make a temporary environment where a copy of the function is stored. I
# do this because we don't want the name of the function to crash with any of
# the names used in this current function definition.
#
# After we have done this, we will assign the correct defining environment to
# fn, namely 'env'.
temporary_environment = new.env()
temporary_environment[[deparse(fn)]] = getFunction(deparse(fn))
environment(temporary_environment[[deparse(fn)]]) = env
# 'as.call' creates a call out of the function name and the list of arguments.
# The call is then evaluated inside the environment 'temporary_environment',
# where the new (copy) of the function f is stored. When f is called, it
# believes that it has been defined inside 'env', so everything works as it
# should.
eval(expr = as.call(c(fn, args)), envir = temporary_environment)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment