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.
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 ofz
will be computed twice, once for a call tog(x, z)
and once for the call to the replacement functiong<-
. 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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With