Home | Blog | Software | Publications | GitHub


Use closure to store variables locally

Assuming following scenario: we first construct a list of functions in a for loop that each function does some job and also tracks the status of each iteration. Later all the functions in the list will be executed sequentially. This scenario is not rare e.g. when making a complex genomic plot with multiple tracks that each track accepts a self-defined graphic function. All self-defined functions are firstly recorded and put into a wrapper object and in the end, a plotting function is applied to execute all the graphic functions. Following code shows an example:

for(i in 1:n_track) {
    new_track = add_new_track(..., fun = function() {
        ...
        cat("this is track", i, "\n")
    })
    obj = add_obj(obj, new_track)
}
plot_all_tracks(obj)

However, i in the self-defined functions does not have the value you expect. Actually the value of i is always n_track. Let's do following simplified experiment:

f_list = list()
for(i in 1:4) {
    f_list[[i]] = function() print(i)
}

for(k in 1:4) {
    f_list[[k]]()
}
## [1] 4
## [1] 4
## [1] 4
## [1] 4

Unfortunately, the function always gets the value of 4 which is the last iteration in the for loop.

The reasion is when the function is defined, the variables inside the function will not be evaluated unless they are executed, so in the function() print(i), the value of i is not recorded when it is defined, and when it is called, it looks for i in the current environment and the four iterations have been already finished and i has the value of 4.

In order to catch the value of i in each iteration, we can use closure to catch it and stored it locally. In following example, a copy of i is stored in a local environment which is associated with the function defined in each iteration. Later on, when the function is executed, it will look for the i in the environment where it is defined and it is exactly the local environment it is associated.

f_list = list()
for(i in 1:4) {
    f_list[[i]] = local({i = i; function() print(i)})
}

for(k in 1:4) {
    f_list[[k]]()
}
## [1] 1
## [1] 2
## [1] 3
## [1] 4