1

I'm trying to put something on CRAN that allows the user to manipulate reactive shiny objects by making an analogous static object on the search path. I know I cannot write to the global environment (what it currently does) but I'm not sure how to let the objects persist once the function is executed.

store_it <- function() {
  env <- new.env()
  assign("x", runif(10), env)
  assign("iris_df", head(iris), env)
  # View(env)
  env
}

# how I want to use it, doesn't work
store_it() # <environment: 0x0000012bd8959cb0>
x          # Error: object 'x' not found
iris_df    # Error: object 'iris_df' not found

# works
e <- attach(store_it())

x
iris_df

It does what I want but I don't like that it keeps adding environments to the search path:

e <- attach(store_it())
# The following objects are masked from store_it() (pos = 3):
#   iris_df, x

e <- attach(store_it())
# The following objects are masked from store_it() (pos = 4):
#   iris_df, x

e <- attach(store_it())
# The following objects are masked from store_it() (pos = 5):
#   iris_df, x

What's the right way to do this? I'd like the user to just write store_it(). If attach() is the right way, how do I put it in the function so it doesn't keep making new environments? Please keep in mind the solution needs to pass CRAN's policies. Thanks in advance.

Note: Someone will likely point out that I asked a similar question in the past. I made a new post because this question is more specific. Package environment manipulation and submitting to CRAN

yake84
  • 3,004
  • 2
  • 19
  • 35

2 Answers2

2

1) CRAN will allow you to store and read back items in the tempdir as shown below or one could omit tmpdir and just use the current directory like R's Rprof does.

store_it <- function() {
    iris_df <- head(iris)
    rand <- runif(3)
    save(list = ls(), file = file.path(tempdir(), "store.rda"))
}

retrieve_it <- function(envir = parent.frame()) {
    load(file.path(tempdir(), "store.rda"), envir = envir)
}

2) It is also possible to store objects in the package itself provided that the package defines an environment to hold them. Thus this would work.

store <- new.env()

store_it2 <- function() {
    store$iris_df <- head(iris)
    store$rand <- runif(3)
    invisible(store)
}

retrieve_it2 <- function(envir = parent.frame()) {
    list2env(as.list(store), envir)
}

An alternative to retrieve_it2 is to just export store in your package and then the user can access its contents as, for example, store$x or with(store, x) to access x or they can attach it using attach(store). Of course if store is exported then both could be provided -- the user can directly access store or they can run retrieve_it2() .

G. Grothendieck
  • 254,981
  • 17
  • 203
  • 341
  • Thanks for this suggestion. If I understand correctly, `retrieve_it()` would write to the global environment and has the chance of overwriting existing objects. From the feedback I had from the CRAN moderators, I don't think this is allowed. – yake84 Apr 30 '20 at 01:53
  • `retrieve_it` loads the store into the environment specified by the `envir` argument which by default is the environment from which it is called but you can specify any environment you like. What it does is perfectly fine. Try putting it in a small package and run `R CMD check --as-cran` and you will see it won't complain. – G. Grothendieck Apr 30 '20 at 03:09
  • Have added a second approach as well. Either approach should work. – G. Grothendieck Apr 30 '20 at 03:23
  • I appreciate your help. Ideally, it'd all happen in a single function. I put a better reprex here. Can you confirm if I'm using it correctly? https://gist.github.com/rjake/c709f0f699f5d4f037dfbb2eb7300783 I had an `envir` parameter that defaulted to `.GlobalEnv` in the function but it didn't pass when CRAN reviewed it. From what I can tell, R CMD Check isn't sensitive enough to catch the issue as it is looking specifically for `assign(..., .GlobalEnv)` Could you explain a little further why `parent.frame()` would be better than `.GlobalEnv`? The end result seems the same. Thanks again. – yake84 Apr 30 '20 at 05:41
  • I am not running RStudio but just eyeballing it it looks ok. `envir = parent.frame()` is an idiom used by many R functions and seems flexible. The user may not wish to muck up their global environment in the first place and this allows them to or not to. I have added some more discussion at the end of the answer. – G. Grothendieck Apr 30 '20 at 10:44
1

One method:

store_it <- local({
  .env <- NULL
  function() {
    if (is.null(.env)) .env <<- new.env(parent = emptyenv())
    .env$x <- runif(10)
    .env$iris_df <- head(iris)
    .env
  }
})
store_it()
# <environment: 0x0000000049340d80>
store_it()
# <environment: 0x0000000049340d80>
attach(store_it())
x
#  [1] 0.6478808 0.6862712 0.6969733 0.9513357 0.1560208 0.9332960 0.3966457
#  [8] 0.5067889 0.4244998 0.3747476
iris_df
#   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
# 1          5.1         3.5          1.4         0.2  setosa
# 2          4.9         3.0          1.4         0.2  setosa
# 3          4.7         3.2          1.3         0.2  setosa
# 4          4.6         3.1          1.5         0.2  setosa
# 5          5.0         3.6          1.4         0.2  setosa
# 6          5.4         3.9          1.7         0.4  setosa

detach()
x
# Error: object 'x' not found
# No traceback available 

It will still "attach" the environment multiple times to your search path, because by calling attach repeatedly, that's what you're telling it to do.

r2evans
  • 141,215
  • 6
  • 77
  • 149