Skip to content

Defining environments within functions

5 messages · Duncan Murdoch, Giles Hooker, Luke Tierney

#
How can I define environments within a function so that they are visible
to calls to a sub-function?

I have defined an objective function,

	ProfileErr = function(params,...)

which I would like to optimize using standard routines (optim,
nlminb,....) but which contains auxiliary variables which need to be
updated along with params. No optimization routine in R that I have
found has facilities for this.

Specifically, within ProfileErr, I need to calculate

  	coefs(params,...)

This a function which requires a further optimization, and I can achieve
significant efficiency gains by starting  where the last optimization
ended, so I would like to keep track of it.

At the command line, I get around this by

	ProfileEnv = new.env()
	assign('coefs',coefs,3,ProfileEnv)

and within ProfileErr, I can call

	startcoefs = get('coefs',envir=ProfileEnv)
	* do the optimization to get newcoefs *
	assign('coefs',newcoefs,3,ProfileEnv)

Then calling

	optim(pars,ProfileErr,....)

works fine. However, when I try to wrap all of that in its own function

	profile.estimate = fn(pars,...){
		ProfileEnv = new.env()
		assign('coefs',coefs,3,ProfileEnv)
	
		res = optim(pars,ProfileErr,....)
	}


ProfileErr no longer sees ProfileEnv. I haven't been able to make much
sense out of the documentation on environments, but is there a way to
make this work? Otherwise I'm back to writing variables out to files.

Many thanks,

Giles


-- 
Giles Hooker
Assistant Professor:

Department of Biological Statistics and Computational Biology
Department of Statistical Science
1186 Comstock Hall
Cornell University
Ithaca, NY, 14853

Ph:  (+1 607) 255 1638
Fax: (+1 607) 255 4698

Email:  giles.hooker at cornell.edu
#
Giles Hooker wrote:
I think you need to give a simplified, runnable example.  (Or at least 
runnable until it hits the scoping problem you've got.)  "Sub-function" 
isn't R terminology, and it's not clear what you mean by it.

In R, you rarely need to work with environments explicitly.  You just 
define functions in the same location and they share the same 
environment.  For example,

fnBuilder <- function(commonArgs) {
  commonVars <- ...
  ProfileErr <- function(params, ...) {}
  coefs <- function(params, ...) {}
  return(list(ProfileErr, coefs))
}

both <- fnBuilder(...)
ProfileErr <- both[[1]]
coefs <- both[[2]]

Now ProfileErr and coefs share the same environment, and both can see 
(and modify) commonArgs and commonVars.

Duncan Murdoch
1 day later
#
Thanks,

I think I over-emphasized the secondary function, but I can generate the 
scoping problem as follows. First, at the command line, I can get a 
function to access objects that were not in its arguments by

ProfileEnv = new.env()
hello.world = "Hello World"
assign('hello.world',hello.world,3,envir=ProfileEnv)

fn1 = function()
{
	hw = get('hello.world',envir=ProfileEnv)
	print(hw)
}

and then call

 > fn1()
[1] "Hello World"


Now I want to define a wrapper function

fn2 = function()
{
	ProfileEnv = new.env()
	hello.world = "Hello World"
	assign('hello.world',hello.world,3,envir=ProfileEnv)

	fn1()
}

and if I try

 > rm(ProfileEnv)                          # Just to be safe
 > rm(hello.world)
 > fn2()
Error in get("hello.world", envir = ProfileEnv) :
   object "ProfileEnv" not found

In my actual code, fn1() is really a call to

optim(pars,ProfileErr,....)

and hello.world are quantities that were calculated the last time that 
ProfileErr was called and that I want to keep track of.

As an alternative simple example, how would I keep a counter for the 
number of times that optim (or any other generic optimizer) has called 
ProfileErr?

giles

  
    
#
On 29/08/2008 6:52 AM, Giles Hooker wrote:
In fn2, you have a local variable called ProfileEnv.  You defined fn1 in 
the global environment, so it can't see it.  Everything would work if 
you defined fn1 within fn2.

A more natural way to do this is not to explicitly use environments. 
Just use the natural lexical scoping in R.  In the example below, I've 
also added a counter variable, to answer your other question.

 > buildfn1 <- function() {
+   hello.world <- "Hello World"
+   counter <- 0
+
+   fn1 <- function() {
+     print(hello.world)
+     counter <<- counter + 1 # note superassignment <<-
+     print(counter)
+   }
+
+   return(fn1)
+ }
 >
 > fn1 <- buildfn1()
 > fn1()
[1] "Hello World"
[1] 1
 > fn1()
[1] "Hello World"
[1] 2

Duncan Murdoch
#
If you want to use this pattern repeatedly you can define something like

     makeFunWithCounter <- function(fun) {
 	counter <- 0
 	list(count = function() counter,
 	     fun = function(...) { counter <<- counter + 1; fun(...)})
     }

and then do

     > fwc <- makeFunWithCounter(function() print("Hello"))
     > f2 <- fwc$fun
     > f2()
     [1] "Hello"
     > f2()
     [1] "Hello"
     > f2()
     [1] "Hello"
     > fwc$count()
     [1] 3

If you only want to do it once you can use local,

     fwc <- local({
 	counter <- 0
 	fun <- function() { counter <<- counter + 1; print("Hello") }
 	list(count = function() counter, fun = fun)
     })

Best,

luke
On Fri, 29 Aug 2008, Giles Hooker wrote: