12

When running a function in R, I run another function within it. I have a code on the lines of this:

f_a <- function(b, c){
    return(b + c)
}

f_e <- function(){
    b = 2
    c = 2 
    d = f_a(b, c)
    print(d)
}

This works fine. What I'd like to do is not pass the variables b, c to the function f_a. I'd like to do something like this (which throws errors)

f_a <- function(){
    return(b + c)
}

f_e <- function(){
    b = 2
    c = 2
    d = f_a()
    print(d)
}

Is there a way to do this using environments or search paths or any other way?

1
  • Are b,c global constants, parameters, attributes of an object, or just arbitrary variables...? If you often need to access certain variables from within certain function(s), isn't that a strong code smell that it should be an object? Commented Jun 25, 2018 at 6:43

4 Answers 4

15

I do encourage you to read about lexical scoping, but I think a good approach to avoid writing a lot of variables could be:

get_args_for <- function(fun, env = parent.frame(), inherits = FALSE, ..., dots) {
    potential <- names(formals(fun))

    if ("..." %in% potential) {
        if (missing(dots)) {
            # return everything from parent frame
            return(as.list(env))
        }
        else if (!is.list(dots)) {
            stop("If provided, 'dots' should be a list.")
        }

        potential <- setdiff(potential, "...")
    }

    # get all formal arguments that can be found in parent frame
    args <- mget(potential, env, ..., ifnotfound = list(NULL), inherits = inherits)
    # remove not found
    args <- args[sapply(args, Negate(is.null))]
    # return found args and dots
    c(args, dots)
}

f_a <- function(b, c = 0, ..., d = 1) {
    b <- b + 1
    c(b = b, c = c, d = d, ...)
}

f_e <- function() {
    b <- 2
    c <- 2
    arg_list <- get_args_for(f_a, dots = list(5))
    do.call(f_a, arg_list)
}

> f_e()
b c d   
3 2 1 5 

Setting inherits = FALSE by default ensures that we only get variables from the specified environment. We could also set dots = NULL when calling get_args_for so that we don't pass all variables, but leave the ellipsis empty.

Nevertheless, it isn't entirely robust, because dots is simply appended at the end, and if some arguments are not named, they could end up matched by position. Also, if some values should be NULL in the call, it wouldn't be easy to detect it.


I would strongly advise against using these below inside an R package. Not only will it be rather ugly, you'll get a bunch of notes from R's CMD check regarding undefined global variables.

Other options.

f_a <- function() {
    return(b + c)
}

f_e <- function() {
    b <- 2
    c <- 2
    # replace f_a's enclosing environment with the current evaluation's environment
    environment(f_a) <- environment()
    d <- f_a()
    d
}

> f_e()
[1] 4

Something like the above probably wouldn't work inside an R package, since I think a package's functions have their enclosing environments locked.

Or:

f_a <- function() {
    with(parent.frame(), {
        b + c
    })
}

f_e <- function() {
    b <- 2
    c <- 2
    f_a()
}

> f_e()
[1] 4

That way you don't modify the other function's enclosing environment permanently. However, both functions will share an environment, so something like this could happen:

f_a <- function() {
    with(parent.frame(), {
        b <- b + 1
        b + c
    })
}

f_e <- function() {
    b <- 2
    c <- 2
    d <- f_a()
    c(b,d)
}

> f_e()
[1] 3 5

Where calling the inner function modifies the values in the outer environment.

Yet another option that is a bit more flexible, since it only modifies the enclosing environment temporarily by using eval. However, there are certain R functions that detect their current execution environment through "daRk magic", and cannot be fooled by eval; see this discussion.

f_a <- function() {
    b <- b + 1
    b + c
}

f_e <- function() {
    b <- 2
    c <- 2
    # use current environment as enclosing environment for f_a's evaluation
    d <- eval(body(f_a), list(), enclos=environment())
    c(b=b, d=d)
}

> f_e()
b d 
2 5 
Sign up to request clarification or add additional context in comments.

Comments

4

One option is to explicitly grab a and b from the calling environment:

f_a <- function(){
    get('b', envir = parent.frame()) + get('c', envir = parent.frame())
}

f_e <- function(){
    b = 2
    c = 2
    d = f_a()
    d
}

f_e()
#> [1] 4

Alternatively, you can use quote to delay evaluation and then eval to evaluate the code in the calling environment, effectively doing the same thing:

f_a <- function(){
    eval(quote(b + c), parent.frame())
}

This is not really a robust way to write code, though, as it limits the possible ways to call f_a successfully. It's much easier to follow code that explicitly passes variables.

6 Comments

I have a lot of functions in my f_a so this would be quite cumbersome and amount to passing the variables as a more viable option. Lexical scoping makes more sense for me
Lexical scoping isn't a choice, it's how R works. Regardless, I'd highly recommend you rethink how you're structuring your code, as all of these approaches can introduce strange behavior because they muck around with where R looks for things.
I'm actually writing a package and all these functions are part of the package. I need to keep switching between a lot of these functions and hence the issue. I'd like to set all environments to one master environment where all my variables are visible to R
This still sounds like a really bad approach which is going to lead to really hard-to-debug scoping bugs. When writing code that is going to be used a lot, verbosity is not necessarily bad.
Adding to @alistaire's comment, a well written software module has low coupling and high cohesion. Based on what you've described in the OP and comments, it would be good spend some time reworking the design of your functions so the things that require more interaction are closer together are in the same function to increase cohesion and reduce coupling, or to abstract the data types into a larger grained object that can be passed back and forth between functions.
|
2

Edit:

@alistaire's suggestion to use quote to construct the expressions brings up this further alternative that seems even less ugly:

expr_env <- new.env()
   expr_env$f_a <- quote(b+c)
   expr_env$f_z <- quote(x+y)

f_e<-function(){
    b=2
    c=2
    d=eval( expr_env$f_a)
    print(d)
}

Would defining the function using local be an acceptable alternative?

 f_e<-function(){
     b=2
     c=2
     d<-local({
          b+c
              })

     print(d)
 }
 f_e()
[1] 4

An alternative would be to only return a parse tree and then finish the evaluation in the environment "local" to the function. This seems "ugly" to me:

expr_list<-function(){  f_a <- quote(b+c)
                        f_z <- quote(x+y)
list(f_a=f_a,f_z=f_z) }

f_e<-function(){
    b=2
    c=2
    d=eval( (expr_list()$f_a))
    print(d)
}

3 Comments

Again, I have a lot of functions in my f_a so this would be quite cumbersome. Lexical scoping makes more sense for me, although this seems like a good alternative too.
Using quote or expression instead of parse will make it (slightly) less ugly
@alistair. Agree and removed the extraneous return() as well.
0

You can assign the variables to global environment and use inside function.

f_a <- function(){
    return(b + c)
}

f_e <- function(){
    assign("b", 2, envir = .GlobalEnv)
    assign("c", 2, envir = .GlobalEnv)
    d = f_a()
    print(d)
}

# > f_e()
# [1] 4

1 Comment

Using the global environment is not a option for my use

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.