0

If you define functions, they are all part of the Global Environment by default. I wonder, whether there is a way, to set a function's parent environmet to the calling environment (while runtime - a function might be called in different places!). Thus, in case of nested functions, it should become possible, to define a variable in only one envrionment and I would expect for an example like

fun1 <- function() {
  # variable "my_env" defined only in this environment
  subfun() # calls subsubfun()
  return(NULL)
} 

an output of exists("my_env", different_environments) like

# [1] "fun1"
# [1] "===="
# [1] TRUE #     Only here: "here", in this function
# [1] FALSE
# [1] FALSE
# [1] " subfun"
# [1] " ======"
# [1] FALSE
# [1] TRUE #     Only here: "parent environment", in calling function
# [1] FALSE
# [1] "  subsubfun"
# [1] "  ========="
# [1] FALSE
# [1] FALSE
# [1] TRUE #     Only here: "parent-parent environment", in the function calling the function

From the docs ?parent.frame

sys.parent returns the number of the parent frame if n is 1 (the default), the grandparent if n is 2, and so on. See also the ‘Note’.
(...) parent.frame(n) is a convenient shorthand for sys.frame(sys.parent(n)) (implemented slightly more efficiently).

Note
Strictly, sys.parent and parent.frame refer to the context of the parent interpreted function. So internal functions (which may or may not set contexts and so may or may not appear on the call stack) may not be counted, and S3 methods can also do surprising things. Beware of the effect of lazy evaluation: these two functions look at the call stack at the time they are evaluated, not at the time they are called. Passing calls to them as function arguments is unlikely to be a good idea.

Some example to start with from here, which does not work, as all functions are part of the global environment.

subfun0 <- function() {
  e <- parent.frame()
  attr(e, "name") <- "my_env"
  assign("my_env", 1,
         envir = parent.frame(),
         inherits = FALSE, immediate = TRUE)
  return(NULL)
}

subsubfun <- function() {
  print("  subsubfun")
  print("  =========")
  print(exists("my_env"))
  print(exists("my_env", parent.frame()))
  env <- parent.frame()
  print(exists("my_env", parent.env(env)))
  return(NULL)
}

subfun <- function() {
  print(" subfun")
  print(" ======")
  print(exists("my_env"))
  print(exists("my_env", parent.frame()))
  env <- parent.frame()
  print(exists("my_env", parent.env(env)))
  subsubfun()
  return(NULL)
}

fun1 <- function() {
  print("fun1")
  print("====")
  subfun0()
  print(exists("my_env"))
  print(exists("my_env", parent.frame()))
  env <- parent.frame()
  print(exists("my_env", parent.env(env)))
  subfun()
  return(NULL)
}

fun1()

(I just realized, that I had a completely wrong picture of "calling environment" in mind and my questions is, could I make "my picture" work in R.)

Christoph
  • 6,841
  • 4
  • 37
  • 89
  • I don't understand why you would need it.. why can't you just pass arguments to your functions? Or why don't you pass the environment as an argument? – Edo Aug 28 '20 at 12:25
  • @Edo I would like track special variables without the need to rewrite every function. I think this would add quite some nice flexibility. The concrete trigger was [this](https://stackoverflow.com/q/63552940/5784831) really good question... – Christoph Aug 28 '20 at 12:32
  • Track..? What do you mean? ps: what about `environment<-` to change the environment of a function? es: `a<-function()i` `b<-function(){i<-1;a()};b()` Returns Error `d<-function(){environment(a)<-environment();i<-1;a()};d()` Returns [1] 1 – Edo Aug 28 '20 at 12:53
  • by "track" you mean that you wanna see how the value of a variable that is passed between several functions changes in time? – Edo Aug 28 '20 at 12:55
  • @Edo You are right, "track" is misleading, I mean you can create a variable, and you know "all the time", that if it exists, it has to exist in a certain environment and a certain name. I tried `environment() <- rlang::caller_env()` but without success. And from [here](https://stackoverflow.com/a/49619767/5784831) I am not sure anymore, whether my plan is impossible. – Christoph Aug 28 '20 at 12:59

1 Answers1

1

With this code you get exactly what you're looking for:

subfun0 <- function() {
    e <- parent.frame()
    attr(e, "name") <- "my_env"
    assign("my_env", 1,
                 envir = parent.frame(),
                 inherits = FALSE, immediate = TRUE)
    return(NULL)
}

subsubfun <- function() {
    print("  subsubfun")
    print("  =========")
    print(exists("my_env"))
    print(exists("my_env", parent.frame()))
    print(exists("my_env", parent.frame(2)))
    return(NULL)
}

subfun <- function() {
    print(" subfun")
    print(" ======")
    print(exists("my_env"))
    print(exists("my_env", parent.frame()))
    print(exists("my_env", parent.frame(2)))
    subsubfun()
    return(NULL)
}

fun1 <- function() {
    print("fun1")
    print("====")
    subfun0()
    print(exists("my_env"))
    print(exists("my_env", parent.frame()))
    print(exists("my_env", parent.frame(2)))
    subfun()
    return(NULL)
}

fun1()
[1] "fun1"
[1] "===="
[1] TRUE
[1] FALSE
[1] FALSE
[1] " subfun"
[1] " ======"
[1] FALSE
[1] TRUE
[1] FALSE
[1] "  subsubfun"
[1] "  ========="
[1] FALSE
[1] FALSE
[1] TRUE
NULL

The point is that: parent.frame(2) is not equal to parent.env(parent.frame())

Edo
  • 7,567
  • 2
  • 9
  • 19
  • Can you explain, why "`parent.frame(2)` is not equal to `parent.env(parent.frame())`"? I added the docs to the question and to me this is not really clear - your result of course is! (See my edit to the question.) – Christoph Aug 28 '20 at 13:58
  • I believe that [here](http://adv-r.had.co.nz/Environments.html) and [here](http://adv-r.had.co.nz/Environments.html#calling-environments) you will find a far better explanation than whatever I could say. In particular, mind the difference between calling environment and binding environment – Edo Aug 28 '20 at 14:19
  • 1
    I meant "calling env and enclosing env". that's where the difference matters for your example. – Edo Aug 28 '20 at 14:26