>>>>> Duncan Murdoch <murdoch.duncan at gmail.com> >>>>> on Sat, 19 Mar 2016 17:57:56 -0400 writes:> On 19/03/2016 12:45 PM, Boris Steipe wrote: >> Dear all - >> >> I need to have a function maintain a persistent lookup table of results for an expensive calculation, a named vector or hash. I know that I can just keep the table in the global environment. One problem with this approach is that the function should be able to delete/recalculate the table and I don't like side-effects in the global environment. This table really should be private. What I don't know is: >> -A- how can I keep the table in an environment that is private to the function but persistent for the session? >> -B- how can I store and reload such table? >> -C- most importantly: is that the right strategy to initialize and maintain state in a function in the first place? >> >> >> For illustration ... >> >> ----------------------------------- >> >> myDist <- function(a, b) { >> # retrieve or calculate distances >> if (!exists("Vals")) { >> Vals <<- numeric() # the lookup table for distance values >> # here, created in the global env. >> } >> key <- sprintf("X%d.%d", a, b) >> thisDist <- Vals[key] >> if (is.na(thisDist)) { # Hasn't been calculated yet ... >> cat("Calculating ... ") >> thisDist <- sqrt(a^2 + b^2) # calculate with some expensive function ... >> Vals[key] <<- thisDist # store in global table >> } >> return(thisDist) >> } >> >> >> # run this >> set.seed(112358) >> >> for (i in 1:10) { >> x <- sample(1:3, 2) >> print(sprintf("d(%d, %d) = %f", x[1], x[2], myDist(x[1], x[2]))) >> } > Use local() to create a persistent environment for the function. For > example: > f <- local({ > x <- NULL > function(y) { > cat("last x was ", x, "\n") > x <<- y > } > }) > Then: >> f(3) > last x was >> f(4) > last x was 3 >> f(12) > last x was 4 > Duncan Murdoch Yes, indeed. Or use another function {than 'local()'} which returns a function: The functions approxfun(), splinefun() and ecdf() are "base R" functions which return functions "with a non-trivial environment" as I use to say. Note that this is *the* proper R way solving your problem. The fact that this works as it works is called "lexical scoping" and also the reason why (((regular, i.e., non-primitive))) functions in R are called closures. When R was created > 20 years ago, this has been the distinguishing language feature of R (in comparison to S / S-plus). Enjoy! - Martin
Martin, All: A very nice point! Perhaps the following may help to illustrate it. g <- function(){ x <- NULL function(y){cat("result is ",x," \n"); x <<- y} }> f <- g()> rm(g) # g is deleted but its environment remains as the environment of f> f(1)result is> f(3)result is 1> f(5)result is 3 Best, Bert Bert Gunter "The trouble with having an open mind is that people keep coming along and sticking things into it." -- Opus (aka Berkeley Breathed in his "Bloom County" comic strip ) On Mon, Mar 21, 2016 at 2:41 AM, Martin Maechler <maechler at stat.math.ethz.ch> wrote:>>>>>> Duncan Murdoch <murdoch.duncan at gmail.com> >>>>>> on Sat, 19 Mar 2016 17:57:56 -0400 writes: > > > On 19/03/2016 12:45 PM, Boris Steipe wrote: > >> Dear all - > >> > >> I need to have a function maintain a persistent lookup table of results for an expensive calculation, a named vector or hash. I know that I can just keep the table in the global environment. One problem with this approach is that the function should be able to delete/recalculate the table and I don't like side-effects in the global environment. This table really should be private. What I don't know is: > >> -A- how can I keep the table in an environment that is private to the function but persistent for the session? > >> -B- how can I store and reload such table? > >> -C- most importantly: is that the right strategy to initialize and maintain state in a function in the first place? > >> > >> > >> For illustration ... > >> > >> ----------------------------------- > >> > >> myDist <- function(a, b) { > >> # retrieve or calculate distances > >> if (!exists("Vals")) { > >> Vals <<- numeric() # the lookup table for distance values > >> # here, created in the global env. > >> } > >> key <- sprintf("X%d.%d", a, b) > >> thisDist <- Vals[key] > >> if (is.na(thisDist)) { # Hasn't been calculated yet ... > >> cat("Calculating ... ") > >> thisDist <- sqrt(a^2 + b^2) # calculate with some expensive function ... > >> Vals[key] <<- thisDist # store in global table > >> } > >> return(thisDist) > >> } > >> > >> > >> # run this > >> set.seed(112358) > >> > >> for (i in 1:10) { > >> x <- sample(1:3, 2) > >> print(sprintf("d(%d, %d) = %f", x[1], x[2], myDist(x[1], x[2]))) > >> } > > > > Use local() to create a persistent environment for the function. For > > example: > > > f <- local({ > > x <- NULL > > function(y) { > > cat("last x was ", x, "\n") > > x <<- y > > } > > }) > > > Then: > > >> f(3) > > last x was > >> f(4) > > last x was 3 > >> f(12) > > last x was 4 > > > Duncan Murdoch > > Yes, indeed. > Or use another function {than 'local()'} which returns a > function: The functions approxfun(), splinefun() and ecdf() > are "base R" functions which return functions "with a > non-trivial environment" as I use to say. > > Note that this is *the* proper R way solving your problem. > > The fact that this works as it works is called "lexical scoping" > and also the reason why (((regular, i.e., non-primitive))) > functions in R are called closures. > When R was created > 20 years ago, this has been the > distinguishing language feature of R (in comparison to S / S-plus). > > Enjoy! - Martin > > ______________________________________________ > R-help at r-project.org mailing list -- To UNSUBSCRIBE and more, see > https://stat.ethz.ch/mailman/listinfo/r-help > PLEASE do read the posting guide http://www.R-project.org/posting-guide.html > and provide commented, minimal, self-contained, reproducible code.
On 21/03/2016 11:19 AM, Bert Gunter wrote:> Martin, All: > > A very nice point! Perhaps the following may help to illustrate it. > > g <- function(){ > x <- NULL > function(y){cat("result is ",x," \n"); x <<- y} > } > > > > f <- g() > > > rm(g) # g is deleted but its environment remains as the environment of fThat's not quite the jargon we use. The environment of g would probably be the global environment. The thing that gets left behind is the evaluation frame (or environment) of the call g(). Its parent environment is the environment of g. Duncan Murdoch> > > f(1) > result is > > > f(3) > result is 1 > > > f(5) > result is 3 > > > Best, > Bert > > > > > > Bert Gunter > > "The trouble with having an open mind is that people keep coming along > and sticking things into it." > -- Opus (aka Berkeley Breathed in his "Bloom County" comic strip ) > > > On Mon, Mar 21, 2016 at 2:41 AM, Martin Maechler > <maechler at stat.math.ethz.ch> wrote: > >>>>>> Duncan Murdoch <murdoch.duncan at gmail.com> > >>>>>> on Sat, 19 Mar 2016 17:57:56 -0400 writes: > > > > > On 19/03/2016 12:45 PM, Boris Steipe wrote: > > >> Dear all - > > >> > > >> I need to have a function maintain a persistent lookup table of results for an expensive calculation, a named vector or hash. I know that I can just keep the table in the global environment. One problem with this approach is that the function should be able to delete/recalculate the table and I don't like side-effects in the global environment. This table really should be private. What I don't know is: > > >> -A- how can I keep the table in an environment that is private to the function but persistent for the session? > > >> -B- how can I store and reload such table? > > >> -C- most importantly: is that the right strategy to initialize and maintain state in a function in the first place? > > >> > > >> > > >> For illustration ... > > >> > > >> ----------------------------------- > > >> > > >> myDist <- function(a, b) { > > >> # retrieve or calculate distances > > >> if (!exists("Vals")) { > > >> Vals <<- numeric() # the lookup table for distance values > > >> # here, created in the global env. > > >> } > > >> key <- sprintf("X%d.%d", a, b) > > >> thisDist <- Vals[key] > > >> if (is.na(thisDist)) { # Hasn't been calculated yet ... > > >> cat("Calculating ... ") > > >> thisDist <- sqrt(a^2 + b^2) # calculate with some expensive function ... > > >> Vals[key] <<- thisDist # store in global table > > >> } > > >> return(thisDist) > > >> } > > >> > > >> > > >> # run this > > >> set.seed(112358) > > >> > > >> for (i in 1:10) { > > >> x <- sample(1:3, 2) > > >> print(sprintf("d(%d, %d) = %f", x[1], x[2], myDist(x[1], x[2]))) > > >> } > > > > > > > Use local() to create a persistent environment for the function. For > > > example: > > > > > f <- local({ > > > x <- NULL > > > function(y) { > > > cat("last x was ", x, "\n") > > > x <<- y > > > } > > > }) > > > > > Then: > > > > >> f(3) > > > last x was > > >> f(4) > > > last x was 3 > > >> f(12) > > > last x was 4 > > > > > Duncan Murdoch > > > > Yes, indeed. > > Or use another function {than 'local()'} which returns a > > function: The functions approxfun(), splinefun() and ecdf() > > are "base R" functions which return functions "with a > > non-trivial environment" as I use to say. > > > > Note that this is *the* proper R way solving your problem. > > > > The fact that this works as it works is called "lexical scoping" > > and also the reason why (((regular, i.e., non-primitive))) > > functions in R are called closures. > > When R was created > 20 years ago, this has been the > > distinguishing language feature of R (in comparison to S / S-plus). > > > > Enjoy! - Martin > > > > ______________________________________________ > > R-help at r-project.org mailing list -- To UNSUBSCRIBE and more, see > > https://stat.ethz.ch/mailman/listinfo/r-help > > PLEASE do read the posting guide http://www.R-project.org/posting-guide.html > > and provide commented, minimal, self-contained, reproducible code.