It has been a while between posts. So, lets dive in with a simple, but technical, one.
The problem with do.call
When developing code in R
, you often want to call a function with combinations of arguments that you don’t know when you write the function code. One good example is when an outer function uses an argument and then updates it for its own needs, but then passes that new argument to an inner function, e.g.,:
<- function(x, y) {
sumXY if (missing(x)) return("x was missing")
if (missing(y)) return("y was missing")
+ y
x
}
<- function(x, ...) {
useAndUpdateY <- list(...)
dots if (!is.null(dots$y)) {
message("y was provided; I want to use y and modify x as a result")
<- dots$y
y <- x * y # update y
y
}# Now, normally, we use '...' formulation in R
sumXY(x = x, ...)
}<- useAndUpdateY(x = 2, y = 3)) # Returns 5 -- Wrong! (out
## y was provided; I want to use y and modify x as a result
## [1] 5
Basically, this returns the wrong answer because the inner function must live with the original values of the ...
, in this case, y = 3
Solution, part 1 – use do.call
We can use do.call
to construct the arguments as a list
, giving us immense flexibility.
<- function(x, y) {
sumXY if (missing(x)) return("x was missing")
if (missing(y)) return("y was missing")
+ y
x
}
<- function(x, ...) {
useAndUpdateY <- list(...)
dots if (!is.null(dots$y)) {
message("y was provided; I want to use y and modify x as a result")
<- dots$y
y <- x * y # update y
y
}# Now, normally, we use '...' formulation in R
do.call(sumXY, list(x = x, y = y))
}<- useAndUpdateY(x = 2, y = 3)) # returns 8! correct! (out
## y was provided; I want to use y and modify x as a result
## [1] 8
The main problem with this is that it evaluates list(x = x, y = y)
before passing it into sumXY
. For small objects, this can be unnoticeable. But for large objects, including, in our experience, all sp::SpatialPolygons
objects, this can be unbearable. In interactive R
sessions (including Rstudio
), the user will lose access to the command prompt for minutes to hours as R
attempts to print
the entire object.
Solution part 2 – use alist
Basically, alist
is almost identical to list
, but it doesn’t evaluate the arguments before passing them along to do.call
. In the help manual for ?alist
, this is stated, but it is under-emphasized:
alist handles its arguments as if they described function arguments. So the values are not evaluated …
<- function(x, y) {
sumXY if (missing(x)) return("x was missing")
if (missing(y)) return("y was missing")
+ y
x
}
<- function(x, ...) {
useAndUpdateY <- list(...)
dots if (!is.null(dots$y)) {
message("y was provided; I want to use y and modify x as a result")
<- dots$y
y <- x * y # update y
y
}# Now, normally, we use '...' formulation in R
do.call(sumXY, alist(x = x, y = y)) # update to `alist`
}<- useAndUpdateY(x = 2, y = 3)) # returns 8! still correct! (out
## y was provided; I want to use y and modify x as a result
## [1] 8
Conclusion
Always use alist
when using do.call
, even for small problems.