Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are arguments to replacement functions not evaluated lazily?

Tags:

r

promise

Consider the following simple function:

f <- function(x, value){print(x);print(substitute(value))}

Argument x will eventually be evaluated by print, but value never will. So we can get results like this:

> f(a, a)  
Error in print(x) : object 'a' not found  
> f(3, a)  
[1] 3  
a  
> f(1+1, 1+1)  
[1] 2  
1 + 1  
> f(1+1, 1+"one")  
[1] 2  
1 + "one"

Everything as expected.

Now consider the same function body in a replacement function:

'g<-' <- function(x, value){print(x);print(substitute(value))}

(the single quotes should be fancy quotes)

Let's try it:

> x <- 3  
> g(x) <- 4  
[1] 3  
[1] 4  

Nothing unusual so far...

> g(x) <- a  
Error: object 'a' not found  

This is unexpected. Name a should be printed as a language object.

> g(x) <- 1+1  
[1] 4  
1 + 1  

This is ok, as x's former value is 4. Notice the expression passed unevaluated.

The final test:

> g(x) <- 1+"one"  
Error in 1 + "one" : non-numeric argument to binary operator  

Wait a minute... Why did it try to evaluate this expression?

Well the question is: bug or feature? What is going on here? I hope some guru users will shed some light about promises and lazy evaluation on R. Or we may just conclude it's a bug.

like image 563
Ferdinand.kraft Avatar asked Mar 06 '13 16:03

Ferdinand.kraft


1 Answers

We can reduce the problem to a slightly simpler example:

g <- function(x, value)
'g<-' <- function(x, value) x
x <- 3

# Works
g(x, a)
`g<-`(x, a)

# Fails
g(x) <- a

This suggests that R is doing something special when evaluating a replacement function: I suspect it evaluates all arguments. I'm not sure why, but the comments in the C code (https://github.com/wch/r-source/blob/trunk/src/main/eval.c#L1656 and https://github.com/wch/r-source/blob/trunk/src/main/eval.c#L1181) suggest it may be to make sure other intermediate variables are not accidentally modified.

Luke Tierney has a long comment about the drawbacks of the current approach, and illustrates some of the more complicated ways replacement functions can be used:

There are two issues with the approach here:

A complex assignment within a complex assignment, like f(x, y[] <- 1) <- 3, can cause the value temporary variable for the outer assignment to be overwritten and then removed by the inner one. This could be addressed by using multiple temporaries or using a promise for this variable as is done for the RHS. Printing of the replacement function call in error messages might then need to be adjusted.

With assignments of the form f(g(x, z), y) <- w the value of z will be computed twice, once for a call to g(x, z) and once for the call to the replacement function g<-. It might be possible to address this by using promises. Using more temporaries would not work as it would mess up replacement functions that use substitute and/or nonstandard evaluation (and there are packages that do that -- igraph is one).

like image 68
hadley Avatar answered Oct 06 '22 01:10

hadley