rglobal-variablesassignr-packagecran

How to change package logic to avoid modifying .GlobalEnv


Background. I am writing a package that I'd like to submit to CRAN (called surveytable). The basic logic of the package is:

To start, the user specifies an object that they want to work with. In this example, the object is called "namcs2019sv".

library(surveytable)
set_survey("namcs2019sv")

Other functions in the package work with this object. Some functions analyze the object (but do not modify it):

tab("AGER")

Other functions modify the object. Here, var_cross() creates a new variable inside the object called "Age x Sex". tab() can then be used to analyze this new variable.

var_cross("Age x Sex", "AGER", "SEX")
tab("Age x Sex")

.GlobalEnv. The way I accomplish this is by using .GlobalEnv. Every function that deals with the object calls get0() at the beginning to get the object. The functions that need to modify the object (like var_cross()), at the end of the function call assign().

When you call assign, you have to specify the environment that you are assigning to. Thus, I used to have assign(*, envir = .GlobalEnv). I read that CRAN doesn't want you to do that, so I changed it to assign(*, envir = getOption("surveytable.survey_envir")), where the "surveytable.survey_envir" option is set to .GlobalEnv.

CRAN does not like this. They write, "Please do not modify the .GlobalEnv. This is not allowed by the CRAN policies."

Question. How can I change my package to comply with the CRAN policy?

The usual approach is to specify the object as an argument in each function call, and to return it for the functions that modify it, like this:

tab("AGER", object = namcs2019sv)
namcs2019sv = var_cross("Age x Sex", "AGER", "SEX", object = namcs2019sv)
tab("Age x Sex", object = namcs2019sv)

I don't want to do this. The whole purpose of setting up the package the way it currently is, is to make tasks easier for users of the package. The intended user just wants to specify the object of interest once, and then they want to issue lots of commands to analyze and / or modify the object. They don't want to keep typing and retyping the object name on every line.

Another approach is, at the beginning of every function, when I call get0(), to also figure out which environment the object is coming from, and then to assign() it to that environment.

When I looked into this, I've found conflicting solutions online, which appear to work sometimes but not others. I have not found a solution as part of a package that I can just use.

Even if I were to do this, I don't know whether it would comply with the CRAN policy though. If I figure out which environment the object is coming from, then I assign() to it, and the environment turns out to be .GlobalEnv, then I think this still contradicts their policy, doesn't it?


Solution

  • Following @AllanCameron 's advice, I added an environment to the package.

    Specifically, all you do is just add the line

    env = new.env()
    

    somewhere in your package's zzz.R file.

    When the user gives me the object that I wish to use / modify later, I just save it to this environment:

    env$object = object
    

    When I need to "get" the object: object = env$object. Make changes to object, then save it back to the package's environment: env$object = object.

    Once you see this pattern, it is so easy.