How to Define "Hidden Global Variables" Inside R Packages

How to define hidden global variables inside R packages?

Thank you for sharing your packages @Dirk Eddelbuettel

The solution for my question is the following:

.pkgglobalenv <- new.env(parent=emptyenv())

exs.time.start<-function(){
assign("exs.time", proc.time()[3], envir=.pkgglobalenv)
return(invisible(NULL))
}

exs.time.stop<-function(restartTimer=TRUE){
if(exists('exs.time',envir=.pkgglobalenv)==FALSE){
stop("ERROR: exs.time was not found! Start timer with exs.time.start")
}
returnValue=proc.time()[3]-.pkgglobalenv$exs.time
if(restartTimer==TRUE){
assign("exs.time", proc.time()[3], envir=.pkgglobalenv)
}
message(paste0("INFO: Elapsed time ",returnValue, " seconds!"))
return(invisible(returnValue))
}
  • I've created an environment with new.env(), inside my R file, before my function definitions.
  • I've used assign() to access the environment and change the value of my global variable!

The variable is hidden and everything works fine! Thanks guys!

Global variable in a package - which approach is more recommended?

Some packages use hidden variables (variables that begin with a .), like .Random.seed and .Last.value do in base R. In your package you could do

e <- new.env()
assign(".sessionId", "xyz123", envir = e)
ls(e)
# character(0)
ls(e, all = TRUE)
# [1] ".sessionId"

But in your package you don't need to assign e. You can use a .onLoad() hook to assign the variable upon loading the package.

.onLoad <- function(libname, pkgname) {
assign(".sessionId", "xyz123", envir = parent.env(environment()))
}

See this question and its answers for some good explanation on package variables.

Define Global Variables when creating packages

There are standard ways to include data in a package - if you want some particular R object to be available to the user of the package, this is what you should do. Data is not limited to data frames and matrices - any R object(s) can be included.

If, on the other hand, your intention was to modify the global environment every time a a function is called, then you're doing it wrong. In R's functional programming paradigm, functions return objects that can be assigned into the global environment by the user. Objects don't just "appear" in the global environment, with the programmer hoping that the user both (a) knows to look for them and (b) didn't have any objects of the same name that they wanted to keep (because they just got overwritten). It is possible to write code like this (using <<- as in your question, or explicitly calling assign as in @abhiieor's answer), but it will probably not be accepted to CRAN as it violates CRAN policy.

Global variables in packages in R

In general global variables are evil. The underlying principle why they are evil is that you want to minimize the interconnections in your package. These interconnections often cause functions to have side-effects, i.e. it depends not only on the input arguments what the outcome is, but also on the value of some global variable. Especially when the number of functions grows, this can be hard to get right and hell to debug.

For global variables in R see this SO post.

Edit in response to your comment:
An alternative could be to just pass around the needed information to the functions that need it. You could create a new object which contains this info:

token_information = list(token1 = "087091287129387",
token2 = "UA2329723")

and require all functions that need this information to have it as an argument:

do_stuff = function(arg1, arg2, token)
do_stuff(arg1, arg2, token = token_information)

In this way it is clear from the code that token information is needed in the function, and you can debug the function on its own. Furthermore, the function has no side effects, as its behavior is fully determined by its input arguments. A typical user script would look something like:

token_info = create_token(token1, token2)
do_stuff(arg1, arg2, token_info)

I hope this makes things more clear.

How to view internal variables of an R package within an R session?

You can get all of the functions (exported and unexported) with ls and asNamespace:

head(ls(envir = asNamespace('data.table')))
# [1] "-.IDate" ":=" "[.data.table" "[.ITime"
# [5] "[<-.data.table" "[<-.IDate"

I'm not positive about your latter point, but I thing system.file has what you want. IIRC anything else that gets installed with the package should be in this location.

head(list.files(system.file(package = 'data.table'), recursive = TRUE))
# [1] "DESCRIPTION" "help" "html" "INDEX" "libs"
# [6] "LICENSE"

Loading object into global environment in R Package using .onLoad()

There should be a word for looking for complex answers amidst obvious solutions. It could NOT have been more obvious.

R code workflow

The first practical advantage to using a package is that it’s easy to
re-load your code. You can either run devtools::load_all(), or in
RStudio press Ctrl/Cmd + Shift + L, which also saves all open files,
saving you a keystroke. This keyboard shortcut leads to a fluid
development workflow:

  1. Edit an R file.
  2. Press Ctrl/Cmd + Shift + L.
  3. Explore the code in the console.
  4. Rinse and repeat.

Congratulations! You’ve learned your first package development workflow. Even if you learn nothing else from this
book, you’ll have gained a useful workflow for editing and reloading R
code

Load_all(). Wow! Just that simple. Load all ran the .onload() function and rendered the objects into global environment. Who knew?

Reference: R Code Workflow, R Packages, Hadley



Related Topics



Leave a reply



Submit