Search code examples
rfunctionr-environment

Is it possible to create a stateful function with a single call in R?


I know that I can create a stateful function adder using the factory function adder_maker per the below:

adder_maker <- function() {x <- 0; function() {x <<- x+1; x}}
adder1 <- adder_maker()
adder1()
adder1()
environment(adder1)

The function increments as expected and sits in its own enclosing environment.

However, if I don't want to store the factory function in an intermediate variable, then the inner function ends up in the global environment.

adder2 <- function() {x <- 0; function() {x <<- x+1; x}}()
adder2()
adder2()
environment(adder2)
  1. Why doesn't adder2 get associated with the environment of its anonymous parent?
  2. If adder2 lives in the global environment, why does it return 1 (instead of Error: object 'x' not found, when attempting to evaluate RHS of the inner assignment, x+1)?
  3. Are there any other clever ways to create a function that behaves like adder1, without assigning a variable for the parent function?

Solution

  • Regarding 1 and 2):

    This has to do with the order of evaluation. Your code was:

    adder2 <- function() {x <- 0; function() {x <<- x+1; x}}()
    

    What gets executed at first here is the R expression {x <- 0; function() {x <<- x+1; x}}. You probably know that the value of an expression in R is the last value within the expression. So in this case, the expression evaluates to an anonymous function (which lives an environment where x <- 0 was defined):

    > {x <- 0; function() {x <<- x+1; x}}
    function() {x <<- x+1; x}
    

    In the next step, this intermediate function gets called (and not the whole thing as you expected!) The result of this intermediate code is of course 1. So what remains is effectively is:

    adder2 <- function() 1
    

    This explains the behavior, and also why it works with parentheses, as noted in comments.

    Regarding 3):

    You are looking for the function local:

    > adder2 <- local({x <- 0; function() {x <<- x+1; x}})
    > adder2()
    [1] 1
    > adder2()
    [1] 2