loopurrr icon indicating copy to clipboard operation
loopurrr copied to clipboard

`for` loop produces different result as `map` due to lazy evaluation

Open TimTeaFan opened this issue 2 years ago • 2 comments

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)

TimTeaFan avatar Apr 04 '22 22:04 TimTeaFan

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 --- #

TimTeaFan avatar Apr 05 '22 12:04 TimTeaFan

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

TimTeaFan avatar Apr 19 '22 19:04 TimTeaFan