loopurrr
loopurrr copied to clipboard
`for` loop produces different result as `map` due to lazy evaluation
R's lazy evaluation can lead to different results when translating map
function calls to for
loops. Below is one example that Claus Wilke provided on Twitter.
Lets think about, whether there is someway to force evaluation programmatically to produce the same result as map
and if not, this needs to be mentioned an a vignette together with other caveats and quirks of as_loop()
.
library(loopurrr)
#> Loading required package: purrr
make_add <- function(n) {
function(x) {
x + n
}
}
idx <- as.list(1:5)
z <- map(idx, make_add)
map(idx, make_add) %>%
as_loop()
# --- convert: `map(idx, make_add)` as loop --- #
out <- vector("list", length = length(idx))
for (i in seq_along(idx)) {
out[[i]] <- make_add(idx[[i]])
}
# --- end loop --- #
z[[1]](10)
#> [1] 11
out[[1]](10)
#> [1] 15
Created on 2022-04-05 by the reprex package (v0.3.0)
One possible way to modify the loop to make it work would be to wrap the rewritten function in eval(bquote())
and evaluate every input with .()
.
# --- convert: `map(idx, make_add)` as loop --- #
out <- vector("list", length = length(idx))
for (i in seq_along(idx)) {
out[[i]] <- eval(bquote(
make_add(idx[[.(i])])
))
}
# --- end loop --- #
Here is a more realistic example of lazy evaluation in a ggplot
for
loop.
library(ggplot2)
out <- vector("list", length = 4)
for (i in 1:4) {
out[[i]] <- ggplot(iris, aes(x = iris[, i])) + geom_histogram(binwidth = 1)
}
# Another alternative to `eval(bquote())` is using `local()` and self-assign `i`:
for (i in 1:4) {
out2[[i]] <- local({
i <- i
ggplot(iris, aes(x = iris[, i])) + geom_histogram(binwidth = 1)
})
}