Skip to content

returning functions inside lapply

6 messages · Ali Tofigh, Duncan Murdoch

#
This has been asked before, but I just cannot figure out why lapply
should behave this way given that R uses lazy evalution. Even after
reading (or at least trying to read) parts of the R language
definition.
[1] 1
[1] 2
[1] 3
[1] 3
[1] 3
[1] 3

Now I know that if I force the evaluation of x inside my function f,
that the behaviour will change, i.e., b[[1]]() will return the value 1
etc. But could some knowledgeable person please tell me how the lazy
evaluation as implemented in R results in, what I preceive to be, a
very strange behaviour when using lapply as above? I thought that
lapply calls f three times and returns a list with whatever f
returned. Is this not so?

/ali
#
On 12-04-24 4:22 PM, Ali Tofigh wrote:
That is so.  In each case, f creates a function that looks in its 
enclosing environment for the variable x to be returned.

That enclosing environment is the evaluation frame of f, where x is the 
argument being passed.

But x is never used in evaluating f, so the promise to evaluate x is 
never forced until you finally call one of those functions.

That means x will refer to some internal variable in lapply (which 
sapply calls).  You'll have 3 different promises to evaluate it, but 
they all evaluate to the same value.

You will get the result you want by forcing evaluation of x in f.  Then 
the three promises get evaluated before the internal value gets changed.

f<- function(x) { force(x); function() {x}}
b<- sapply(1:3, f)
b[[1]]()  # gives 1

You should almost always force evaluation of arguments before returning 
a constructed function to avoid problems like you had.

Duncan Murdoch
#
On Tue, Apr 24, 2012 at 16:57, Duncan Murdoch <murdoch.duncan at gmail.com> wrote:
Thank you Duncan for you reply! However, I'm not sure I understand
this last explanation. This is what I think you mean lapply does.
Pleas correct me if I'm wrong.

1) lapply uses the same variable name as the argument in my function
(in this case 'x')
2) lapply uses this 'x' variable to set up a bunch of unevaluated calls
3) finally, lapply evaluates all the calls. but at this point,
lapply's 'x' variable is set to the last element of the list that was
passed to it and all the unevaluated calls will now use this value as
their 'x'?

/Ali
#
On 12-04-24 5:13 PM, Ali Tofigh wrote:
No, lapply can use any name it likes, or no name at all (e.g. just an 
expression like 1+y).
No, lapply calls f a bunch of times, passing this expression each time.
When lapply evaluates f, it produces a function that refers to the local 
variable x in each evaluation frame.  That's a different variable each 
time, but in all cases it's a promise to evaluate the expression that 
lapply passed in.  So if lapply passed 1+y as the expression, x becomes 
a promise to evaluate 1+y in lapply's evaluation frame.

Since you don't call any of those functions until after lapply is done, 
its evaluation frame will remain.  It's hard to get to it, but each of 
those promises references it.

When you finally evaluate x in one of those functions, it goes and 
evaluates 1+y in the lapply frame (or whatever the expression was). 
That evaluates to 3.  It could have evaluated to 17 if lapply had done 
something stupid like setting y to 16 after calling f in the loop, but R 
functions never do stupid things, so you don't need to worry about that.

Duncan Murdoch
#
Many thanks to both Duncan and Bert for clarifying this. Having looked
carefully at what you both wrote, my conclusion is the following:

in this code below

f <- function(x) {function() {x}}
b <- lapply(1:3, f)

lapply does not actually call f with the values 1, 2, and 3. Instead,
it calls f three times with the same object that, had only f used x
for something, would have evaluated to 1 in the first call and to 2 in
the second etc. but since f does not use x, the expression passed to f
is never actually evaluated until the first time one of the functions
in b is called. But at that point, that object will evaluate to 3 for
all three functions in b.

so what lapply is doing resembles:
[1] 3
[1] 3
[1] 3

Thanks again!
/Ali
On Tue, Apr 24, 2012 at 18:37, Duncan Murdoch <murdoch.duncan at gmail.com> wrote:
#
On 12-04-24 7:34 PM, Ali Tofigh wrote:
Yes, I think that's exactly right.

Duncan Murdoch