jenny Tue Mar 29 12:29:36 2016
I tweeted a link to my Draw the rest of the owl write-up and got some free code review. Konrad Rudolph pointed out that I really should use force()
on n
and VERB
inside the function factory, to make sure I can't get burned by lazy evaluation. Reading more on that drew my attention to the function operators section of Advanced R, which is clearly relevant to my problem: "A function operator is a function that takes one (or more) functions as input and returns a function as output." Oops.
So I re-read function operators and my head exploded a little. It is probably just as well that I worked off the dorky power()
example, which is much easier to understand and got me most of the way.
But what exacty is the issue with lazy evaluation and force()
?
My non-deterministic VERB will just be frustrating here, so I'm going back to simple examples.
call_me_n <- function(f, n = 3) {
function(...) for (i in seq_len(n)) f(...)
}
a_fun <- function() cat("calling a_fun()!\n")
b_fun <- function() cat("calling b_fun()!\n")
I set n
and enclosed_fun
in the global environment. Then I apply call_me_n()
to them.
n <- 2
enclosed_fun <- a_fun
a_2 <- call_me_n(enclosed_fun, n)
a_2()
#> calling a_fun()!
#> calling a_fun()!
Now I change n
and enclosed_fun
in the global environment and call my_fun()
again. I am expecting horrible things to happen to me. Namely, I expect to see b_fun()
executed 4 times.
n <- 4
enclosed_fun <- b_fun
a_2()
#> calling a_fun()!
#> calling a_fun()!
And I do not. Why not?
Now Kevin Ushey explains: The problem here is with promises -- when you call a_2()
the first time, the promises passed in (n
, enclosed_fun
) get forced, and so a_2
gets its own copies of those guys.
Therefore to see the problem I need to apply my function factory but redefine n
and/or enclosed_fun
before actually invoking the thing it has produced.
n
#> [1] 4
enclosed_fun
#> function() cat("calling b_fun()!\n")
b_4 <- call_me_n(enclosed_fun, n)
BUT WAIT! Before I actually call b_4()
, I redefine n
and enclosed_fun
:
(n <- 3)
#> [1] 3
(enclosed_fun <- a_fun)
#> function() cat("calling a_fun()!\n")
b_4()
#> calling a_fun()!
#> calling a_fun()!
#> calling a_fun()!
There we go! I have finally correctly done the wrong thing.
How do we fix this? force()
inside the factory.
call_me_n <- function(f, n = 3) {
force(f)
force(n)
function(...) for (i in seq_len(n)) f(...)
}
n
#> [1] 3
enclosed_fun
#> function() cat("calling a_fun()!\n")
a_3 <- call_me_n(enclosed_fun, n)
(n <- 6)
#> [1] 6
(enclosed_fun <- b_fun)
#> function() cat("calling b_fun()!\n")
a_3()
#> calling a_fun()!
#> calling a_fun()!
#> calling a_fun()!
This time a_3()
honors the values of n
and enclosed_fun
upon its definition, as opposed to their current values in the global environment.
# gistr::gist_create("beware-lazy-eval.R",
# description = "How lazy evaluation can bite you",
# public = FALSE, knit = TRUE, include_source = TRUE)
The problem here is with promises -- when you call
my_fun()
the first time, the promises passed in (n
,enclosed_fun
) get forced, and somy_fun
gets its own copies of those guys (witha_fun
). If you don't force those promises (ie, don't callmy_fun()
and later redefinen
andenclosed_fun
, then you'll seeb_fun()
getting called.You can avoid this kind of problem by forcing the promises on creation, e.g.
In other words, given the code:
Since
promise
isn't actually evaluated infoo
's body (it only appears in the body of a nested function; those expressions don't get evaluated),promise
won't actually get assigned a value untilfoo
is called. You can convince yourself with e.g.