4

I have a function who's task is to create a variable in the parent object. What I want is for the function to create the variable at the level at which it's called.

createVariable <- function(var.name, var.value) {
    assign(var.name,var.value,envir=parent.frame())
    }


# Works
testFunc <- function() {
    createVariable("testVar","test")
    print(testVar)
}

# Doesn't work
testFunc2 <- function() {
    testFunc()
    print(testVar)
}

> testFunc()
[1] "test"
> testFunc2()
[1] "test"
Error in print(testVar) : object 'testVar' not found

I'm wondering if there's any way to do this without creating the variable in the global environment scope.

Edit: Is there also a way to unit test that a variable has been created?

Shuo
  • 491
  • 1
  • 6
  • 16

4 Answers4

8

Try this:

createVariable <- function(var.name, var.value) {
  assign(var.name,var.value,envir=parent.env(environment()))
}

Edit: Some more details here and here. With the initial solution, the variable is created in the global env because parent.env is the environment in which the function is defined and the createVariable function is defined in the global environment.

You might also want to try assign(var.name,var.value,envir=as.environment(sys.frames()[[1]])), which will create it in the highest test function calling createVariable in your example (first one on the call stack), in that case however, you will need to remove print(testVar) from testFunc when you call testFunc2 because the variable only be created in the environment of testFunc2, not testFunc. I don't know if that's what you mean by at the level at which it's called.

If you run this:

createVariable <- function(var.name, var.value) {
  assign(var.name,var.value,envir=as.environment(sys.frames()[[1]]))
  print("creating")
}



testFunc <- function() { 
  createVariable("testVar","test")
  print("func1")
  print(exists("testVar"))
}

testFunc2 <- function() {
 testFunc()
 print("func2")
 print(exists("testVar"))
}

testFunc()
testFunc2()

You get

> testFunc()
[1] "creating"
[1] "func1"
[1] TRUE
> testFunc2()
[1] "creating"
[1] "func1"
[1] FALSE
[1] "func2"
[1] TRUE

Which means testVar is in testFun2's environment, not in testFunc's. Creating a new environment as others say might be safer.

Community
  • 1
  • 1
NicE
  • 21,165
  • 3
  • 51
  • 68
2

You need the parent environment to do this, not the calling environment:

createVariable <- function(var.name, var.value) {
  #use parent.env(environment())
  assign(var.name,var.value,envir=parent.env(environment()))
}


> testFunc()
[1] "test"

> testFunc2()
[1] "test"
[1] "test"
LyzandeR
  • 37,047
  • 12
  • 77
  • 87
2

Why do you want to do this? Using assign can lead to hard to find bugs and other problems.

What might be a better approach is to create a new environment just before calling your function of interest. Then in your function assign into that new environment (best if the environment is passed to the function, but can use lexical scoping as well to access it). Then when the function returns you will have the new variable in the environment:

createVariable <- function(var.name, var.value, env) {
  env[[var.name]] <- var.value
}

testfunc <- function() {
  tmpenv <- new.env()
  createVariable('x', 1:10, tmpenv)
  print(ls())
  print(ls(env=tmpenv))
  print(tmpenv$x)
}

If createVariable were defined inside of testfunc then it could access tmpenv directly without needing it passed down (but passing down is safest when possible).

This version of createVariable could even be used more directly to assign in the environment of the calling function (but this becomes more dangerous, too easy to overwrite something in the current environment, or trying to access something by the wrong name due to a small typo):

testfunc2 <- function() {
  createVariable('y', 5:1, environment())
  print(ls())
  print(y)
}
Greg Snow
  • 48,497
  • 6
  • 83
  • 110
0

If you create a new environment and assign the value to it:

my.env <- new.env()
my.env$my.object <- runif(1)

Then call it using get:

> my.object # not found in global env
Error: object 'my.object' not found
> get("my.object", envir = my.env)
[1] 0.07912637

For your function:

createVariable <- function(env.name, var.name, var.value) {
  env.name <- new.env()
  assign(var.name, var.value, envir = env.name)
}
mlegge
  • 6,763
  • 3
  • 40
  • 67