Search code examples
rlazy-evaluationmagrittr

R: Piping input to a function factory


In R, I have a function that outputs a function (a function factory, as it is called). However, when piping an input to it with %>%, strange errors result.

Here is a reduced example:

ax <- function(a) {
  function(x) a*x
}

library(magrittr)
3 %>% ax() %>% plot # Error in a * x : non-numeric argument to binary operator
ax(3) %>% plot      # works, a plot of y=3x is produced

Hadley's book led me to try inserting force(a) into the function body of ax. Now, a plot is produced in both cases.

But I am confused as to why this works. To me, the two cases are essentially identical. Why then is force() needed for the first case, and not the second?


Solution

  • It's because of this bug: https://github.com/tidyverse/magrittr/issues/195

    Update: Install the dev version of magrittr. Apparently this issue was already fixed, but no one had realized that. remotes::install_github("tidyverse/magrittr")

    Nobody has touched the pipe code for two years.

    They underlying issue is R Lazy evaluation. When you pass an argument to a function it is not evaluated until it is needed.

    For an example of this, try this code:

    funs <- list()
    for (i in 1:3) {
      funs[[i]] <- ax(i)
    }
    # i is not evaluated until now. it has been modified before it is evaluated so all the calls evaluate a to the current value of i (i=3)
    print(funs[[3]](1))
    print(funs[[2]](1))
    print(funs[[1]](1))
    # lapply creates an environment for each iteration. Even though the evaluation is delayed, it maps to the expected value of a
    funs2 <- lapply(1:3, ax)
    print(funs2[[3]](1))
    print(funs2[[2]](1))
    print(funs2[[1]](1))
    

    If you add force(a) you force the evaluation of a when ax is called, not when funs[[i]] is called, getting the expected behaviour.

    If I had traced exactly why the bug happens I would probably have submitted a pull request to magrittr already. Maybe it's worth spending some time debugging that... Or maybe someone will soon implement an rlang-based pipe, with a smaller and easier to follow implementation, that fixes most of magrittr shortcomings. (See the issues in the magrittr repository for more details)