Search code examples
rscope

how to view modified environment in R


In this R question, lexical scoping is used to implement a stateful function. The setter and getter methods for a point "class" work as expected to change the coordinates of the point, a "class variable":

point <- function(x, y){
  structure(class = "point", list(
    x = x,
    y = y,
    get_x = function() paste("(", x,",",y,")"),
    set_x = function(x, y){ 
      x <<- x
      y <<- y
    }
  ))
}

x <- 0
y <- 1

p <- point(0,1)
p$get_x()
#[1] "( 0 , 1 )"

p$set_x(6,5)
p$get_x()
#[1] "( 6 , 5 )"

x
#[1] 0

y
#[1] 1

However (as the comments point out) the variables for the coordinates referenced by p look the same, even after calling the setter:

> p
 $x
 [1] 0

 $y
 [1] 1

 $get_x
 function() paste('(', x,',',y,')')
 <environment: 0x557c984f9a48>

 $set_x
 function(x, y){
                       x <<- x
                       y <<- y
                   }
 <environment: 0x557c984f9a48>

 attr(,"class")
 [1] "point"

How does this work? Is R modifying the locally defined x,y or the ones passed in as parameters? Where is the class variable stored that has been modified? Is there a way to see it in R?


Solution

  • The get_x and set_x functions cannot access the x in the structure. When get_x tries to access x it cannot find it in get_x so it looks to the lexical environment in which get_x was defined which is the runtime environment of point which was created when point was invoked and there it finds the argument x. When set_x is run it tries to set x in that same lexical environment and it sets x there, not in the structure. The structure is not modified.

    The variables that get_x and set_x access can be listed like this:

    p <- point(0,1)
    ls(environment(p$set))
    ## [1] "x" "y"
    

    The x and y in the structure are set from the arguments of point and they are never changed after that.

    If point were called again that would not have any effect on the runtime environment that was created from the first invocation of point as it would create a new independent runtime environment.

    Perhaps it is easier to understand if we rewrite it using an explicit environment. This is equivalent to the version in the question.

    point2 <- function(x, y){
      e <- environment()
      structure(class = "point", list(
        x = x,
        y = y,
        get_x = function() paste("(", e$x, ",", e$y, ")"),
        set_x = function(x, y){ 
          e$x <- x
          e$y <- y
        }
      ))
    }
    

    To actually write this use an environment rather than a list. This is similar but uses the runtime environment created by the invocation of point3.

    point3 <- function(x, y) {
      get_x <- function() x
      set_x = function(x, y) { x <<- x; y <<- y }
      structure(environment(), class = "point")
    }
    
    p <- point3(0, 1)
    str(eapply(p, c))
    ## List of 4
    ##  $ get_x:function ()  
    ##   ..- attr(*, "srcref")= 'srcref' int [1:8] 2 16 2 27 16 27 2 2
    ##   .. ..- attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment: 0x0000028d7599c8b8> 
    ##  $ set_x:function (x, y)  
    ##   ..- attr(*, "srcref")= 'srcref' int [1:8] 3 15 3 49 15 49 3 3
    ##   .. ..- attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment: 0x0000028d7599c8b8> 
    ##  $ x    : num 0
    ##  $ y    : num 1
    
    p$set_x(5, 6)
    str(eapply(p, c))
    ## List of 4
    ##  $ get_x:function ()  
    ##   ..- attr(*, "srcref")= 'srcref' int [1:8] 2 16 2 27 16 27 2 2
    ##   .. ..- attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment: 0x0000028d7599c8b8> 
    ##  $ set_x:function (x, y)  
    ##   ..- attr(*, "srcref")= 'srcref' int [1:8] 3 15 3 49 15 49 3 3
    ##   .. ..- attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment: 0x0000028d7599c8b8> 
    ##  $ x    : num 5
    ##  $ y    : num 6
    

    Alternately use a list as in the question but do not include x and y in the structure and just rely on the x and y in point which is what it does anyways. For more on this run

    demo(scoping)