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