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?
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)