2

UPDATE: I have added a variant of Roland's implementation to the kimisc package.

Is there a convenience function for exporting objects to the global environment, which can be called from a function to make objects available globally?

I'm looking for something like

export(obj.a, obj.b)

which would behave like

assign("obj.a", obj.a, .GlobalEnv)
assign("obj.b", obj.b, .GlobalEnv)

Rationale

I am aware of <<- and assign. I need this to refactor oldish code which is simply a concatenation of scripts:

input("script1.R")
input("script2.R")
input("script3.R")

script2.R uses results from script1.R, and script3.R potentially uses results from both 1 and 2. This creates a heavily polluted namespace, and I wanted to change each script

pollute <- the(namespace)
useful <- result

to

(function() {
pollute <- the(namespace)
useful <- result
export(useful)
})()

as a first cheap countermeasure.

Community
  • 1
  • 1
krlmlr
  • 25,056
  • 14
  • 120
  • 217
  • I am lost with your edit. You have concatenated scripts , you want to refactor them. How ? useful is a script or a result of a script?Sorry, I can't get your rationale. – agstudy Jul 05 '13 at 09:34
  • I gave it another try, but probably still don't understand your usecase. – Roland Jul 05 '13 at 09:47
  • A closely related matter: Suppose the exported objects are to be included among the exported objects of a package. Can this be done with roxygen2 without writing a lot of boilerplate `#' @export`'s manually? Cf. https://stackoverflow.com/questions/39917807/export-a-list-of-functions-with-roxygen2 – egnha Oct 16 '16 at 19:05

3 Answers3

8

Simply write a wrapper:

myexport <- function(...) {
  arg.list <- list(...)
  names <- all.names(match.call())[-1]
  for (i in seq_along(names)) assign(names[i],arg.list[[i]],.GlobalEnv)
}

fun <- function(a) {
  ttt <- a+1
  ttt2 <- a+2
  myexport(ttt,ttt2)
  return(a)
}

print(ttt)
#object not found error
fun(2)
#[1] 2
print(ttt)
#[1] 3
print(ttt2)
#[1] 4

Not tested thoroughly and not sure how "safe" that is.

Roland
  • 127,288
  • 10
  • 191
  • 288
  • See my edit for a better explanation of what I'm looking for. – krlmlr Jul 05 '13 at 09:27
  • @Thomas: Does this mean that `assign(..., .GlobalEnv)` is generally more robust? – krlmlr Jul 05 '13 at 09:28
  • Roland: Yes, that's about what I was looking for. I was just wondering if someone has made the effort before and put such a function into an R package. Otherwise I'd put this into my own [this-and-that package](https://github.com/krlmlr/kimisc) if you permit. – krlmlr Jul 05 '13 at 09:58
  • I don't mind if you put it in your package, but as I mentioned it's not really tested. – Roland Jul 05 '13 at 10:15
  • I have added [a variant of your implementation](https://github.com/krlmlr/kimisc/blob/master/pkg/R/export.R) to my [package](https://github.com/krlmlr/kimisc). – krlmlr Jul 23 '13 at 02:05
  • @krlmr -- this approach is very similar to `knitr:::copy_env`. – mnel Jul 23 '13 at 02:14
3

You can create an environment variable and use it within your export function. For example:

env <- .GlobalEnv      ## better here to create a new one :new.env()
exportx <- function(x)
{
  x <- x+1
  env$y <- x
}

exportx(3)
y
[1] 4

For example , If you want to define a global options(emulate the classic R options) in your package ,

my.options <- new.env()
setOption1 <- function(value) my.options$Option1 <- value

EDIT after OP clarification:

You can use evalq which take 2 arguments :

envir the environment in which expr is to be evaluated enclos where R looks for objects not found in envir.

Here an example:

env.script1 <- new.env()
env.script2 <- new.env()
evalq({
  x <- 2
  p <- 3 
  z <- 5 
} ,envir = env.script1,enclos=.GlobalEnv)

evalq({
  h <- x +2
} ,envir = env.script2,enclos=myenv.script1)`

You can see that all variable are created within the environnment ( like local)

 env.script2$h
[1] 4
env.script1$p
[1] 3
> env.script1$x
[1] 2
agstudy
  • 119,832
  • 17
  • 199
  • 261
0

First, given your use case, I don't see how an export function is any better than using good (?) old-fashioned <<-. You could just do

(function() {
    pollute <- the(namespace)
    useful <<- result
})()

which will give the same result as what's in your example.

Second, rather than anonymous functions, it seems better form to use local, which allows you to run involved computations without littering your workspace with various temporary objects.

local({
    pollute <- the(namespace)
    useful <<- result
})

ETA: If it's important for whatever reason to avoid modifying an existing variable called useful, put an exists check in there. The same applies to the other solutions presented.

local({
    .....
    useful <- result
    if(!exists("useful", globalenv())) useful <<- useful
})
Hong Ooi
  • 56,353
  • 13
  • 134
  • 187
  • 1. What if `useful` is already defined in the environment? 2. Thanks for the hint on `local`, indeed more readable than an anonymous function call. But still I need an `export` function here... – krlmlr Jul 05 '13 at 10:35
  • All the other solutions given here will also trash an existing version of `useful`. Didn't you realise that? – Hong Ooi Jul 05 '13 at 10:41
  • I don't mind overwriting `useful` in the global environment. But what if `useful` also exists in the local environment? Isn't `assign` more robust in this case? – krlmlr Jul 05 '13 at 10:54
  • If `useful` is in a package environment, by default its value will be locked (can't be changed). In that case, `<<-` will do the assignment in the global environment. See `?<<-`. – Hong Ooi Jul 05 '13 at 10:56
  • But if the goal is to assign to the global environment `<<-` is imprecise; it's not a global assignment operator and could create unintended results if used inside a function that uses, e.g., `useful` locally. – Thomas Jul 05 '13 at 10:57
  • Also, if this is to be part of a package for external consumption, you do _not_ want to be assigning _anything_ into the global environment. That's pretty rude. I'm aware there are some packages that do this, but they are exceptions, and they also only create objects that the user explicitly asks for. – Hong Ooi Jul 05 '13 at 10:58
  • @HongOoi: This is for an analysis script, not for package code. – krlmlr Jul 05 '13 at 10:58
  • @Thomas: Thank you, this is what I also read from `?\`<<-\``. – krlmlr Jul 05 '13 at 10:59
  • All the solutions thus far will assign into the global environment. However, if you're only talking about local environments, why are you worried about pollution? Everything in the local environment will disappear when the enclosing function returns! – Hong Ooi Jul 05 '13 at 11:01
  • That is to say: I'm not seeing what the point would be of doing an `export` from an environment whose enclosing environment is not the global workspace. OP is worried about pollution in the global workspace, hence the desire to put code inside anonymous environments. But _within_ an anonymous environment, pollution shouldn't be a problem. You wouldn't want to wrap things further within each of these environments, and hence the stated use case would be irrelevant. – Hong Ooi Jul 05 '13 at 11:25