roopclosures

Create a separated copy of an R closure


Is there a way to copy a closure to a new object that contains the current status of the original closure while also being separated for all upcoming changes in the environment?

I have tried to come up with a solution, but it fails with an error when I try using the contained function: Error in { : could not find function "{"

Here's a code example:

# basic incrementation closure
inc <- function() {
  i <- 0 
  
  function(x = 1) {
    i <<- i + x
    i
  }
}

copyClosure <- function(x) {
  oldEnv <- environment(x)
  
  # set up copy of x in new env
  ret <- x
  newEnv <- new.env(parent = emptyenv())
  environment(ret) <- newEnv
  
  # copy objects from old to new env
  objs <- ls(envir = oldEnv, all.names = TRUE, sorted = FALSE)
  for (ii in objs) assign(x = ii, envir = newEnv, value = get(x = ii, envir = oldEnv))
  
  return(ret)
}

# original closure object
foo <- inc()
foo(5)

bar <- copyClosure(foo)

# check if objects i are identical
identical(x = get(x = "i", envir = environment(foo)),
          y = get(x = "i", envir = environment(bar)))

# check if objects i are separated
foo(5)
identical(x = get(x = "i", envir = environment(foo)),
          y = get(x = "i", envir = environment(bar)))

# try using copied closure
bar(5)


Solution

  • Change the parent of the new environment to match the old environment

    newEnv <- new.env(parent = parent.env(oldEnv))
    

    Then it seems to work as intended.

    foo <- inc()
    foo(5)
    # [1] 5
    bar <- copyClosure(foo)
    identical(x = get(x = "i", envir = environment(foo)),
              y = get(x = "i", envir = environment(bar)))
    # [1] TRUE
    foo(5)
    # [1] 10
    identical(x = get(x = "i", envir = environment(foo)),
              y = get(x = "i", envir = environment(bar)))
    # [1] FALSE
    bar(5)
    # [1] 10