1 Comment
Guest *Carl Witthoft* @ 2017-05-19 17:02:52 originally posted:
This is explained in the help page: "This is a ‘special’ primitive function: it only evaluates the argument add." Since the "expr" argument is not evaluated but passed along, you can't swap things around because the usual argument structure of "name = defaultvalue" doesn't apply.
Yes, I had read the help page a couple of times before I wrote this post, but I don't think it explained the puzzle. I cannot follow the logic "since A, so B, because C", where A = "'expr' is not evaluated", B = "you can' swap arguments", and C = "the usual rule doesn't apply". It seems you were saying A leads to C, and C leads to B? I said in this post that I guess was C was the reason for B, but I don't get why A leads to C.
If the help page says, "it only evaluates the argument add as the second argument of the function call", I would not be puzzled. You cannot say because an argument is not evaluated, you cannot pass values by names, e.g. x is not evaluated in the following function but I can still pass 1 to y:
> f = function(x, y) { y }
> f(y = 1, )
[1] 1
So I was wondering what the fundamental magic was.
BTW, it is still unclear to me why print(1) was executed twice in the example in my post.
Originally posted on 2017-05-19 19:01:05
Guest *dracodoc .* @ 2017-05-19 19:58:17 originally posted:
I think the help sentence focused on "special" part that it only evaluate one argument, but real reason is just the primitive function part. They call C code and can have different argument matching rules.
http://adv-r.had.co.nz/Functions.html#function-components
... can have different rules for argument matching (e.g., switch and call). This, however, comes at a cost of behaving differently from all other functions in R.
Another information source:
https://stat.ethz.ch/R-manual/R-devel/library/base/html/match.call.html
match.call does not support primitive functions (where argument matching is normally positional).
Guest *dracodoc .* @ 2017-05-19 20:14:54 originally posted:
> f2 <- function() {
+ on.exit(cat("test"), print(1))
+ }
> f2()
[1] 1
test
Because of position matching of arguments, "print(1)" is the "add" parameter (TRUE), and "cat("test")" is the expression to execute. The first [1] 1 is the side effect of evaluating "print(1)".
> f2 <- function() {
+ on.exit(add = cat("test"), print(1))
+ }
> f2()
[1] 1
[1] 1
Now the expression to execute is an assignment. Normally the assignment should return the value of assignment.
# cat function return NULL so the expression return NULL
> x <- {add = cat(TRUE)}
TRUE
> x
NULL
# print return its argument
> x <- {add = print(TRUE)}
[1] TRUE
> x
[1] TRUE
> x <- {add = print(FALSE)}
[1] FALSE
> x
[1] FALSE
Why it have different behavior in on.exit()? Probably because on.exit is primitive function and is special...
Wow. Thanks for the explanation! That makes more sense to me. I didn't realize add = something could be treated as a whole expression (i.e. {add = something}) instead of passing something to the add argument.
In the second example above, I understand why the first [1] 1 was printed, but how about the second one?
In the same example, if add = cat("test") is the expression to be executed on exit, why didn't we see the side effect "test" in the output?
Originally posted on 2017-05-19 20:33:39
Another piece of code to verify your theory of assignment:
f = function(){
add = FALSE
on.exit(add = TRUE, print(1))
function() {
add
}
}
If add = TRUE means assignment, the value of add should be changed to TRUE, but it does not seem to be true:
> g = f(); g()
[1] 1
[1] 1
[1] FALSE
You can compare it with:
f = function(){
add = FALSE
on.exit({
add = TRUE
}, print(1))
function() {
add
}
}
> g = f(); g()
[1] 1
[1] TRUE
Originally posted on 2017-05-19 20:39:56
I see. That is very helpful. Thanks a lot!
Originally posted on 2017-05-19 20:47:01
Guest *dracodoc .* @ 2017-05-19 20:47:57 originally posted:
Another interesting test
> f3 <- function() {
+ on.exit(add <- cat("test"), print(1))
+ }
> f3()
[1] 1
test
So this "add <- cat("test")" is behaving in more expecting way.
Another example
> f4 <- function(par1, par2){
+ print(par1)
+ print(par2)
+ }
>
# With normal R functions, "=" in argument expression will be interpreted for argument matching first, so this doesn't work unless we wrap with {}
> f4(add = cat("test"), TRUE)
Error in f4(add = cat("test"), TRUE) :
unused argument (add = cat("test"))
# wrapped with {}, expression worked, side effect first, return value second. cat don't print new line so they are in same line.
> f4({add = cat("test")}, TRUE)
testNULL
[1] TRUE
> f4({add <- cat("test")}, TRUE)
testNULL
[1] TRUE
I can only say "on.exit" is special primitive, so I can predict behavior of the normal R functions above, but not for "on.exit".
Do you find using "<-" have some advantages over "=" now? :P
Guest *dracodoc .* @ 2017-05-19 20:57:14 originally posted:
I thought about similar test when I was authoring previous reply.
f = function(){
add = FALSE
on.exit(add <- TRUE, print(1))
function() {
add
}
}
> g = f(); g()
[1] 1
[1] TRUE
So we still have some argument parsing here, but different from normal R functions:
- explicit assignment expression using "<-" works as expected.
- if you wrap "=" with {} so it will not be confused as an named argument, it's also assignment expression.
- using "=" without {} in other normal R function, will have argument name matching, like the example I showed earlier, which cause error if that argument name doesn't exist in function.
- using "=" without {} in on.exit have another interpretation approach, probably related to the C code.
I wouldn't say that is an "advantage" of <-, and these examples are not surprising to me at all :)
Originally posted on 2017-05-19 21:34:07
I totally understand 1-3. Basically the whole point of my post was about 4, which remains a mystery to me, but I don't really care much about it, since I have learned that I should not put add before expr.
Originally posted on 2017-05-19 21:37:31
Guest *dracodoc .* @ 2017-05-19 22:51:27 originally posted:
Sorry I might being too "educational" while trying to state things clear...
I just found this "sys.on.exit()" can answer some of mysteries:
> f2 <- function() {
+ on.exit(add = cat("test"), print(3))
+ sys.on.exit()
+ }
> f2()
[1] 3
[1] 3
print(3)
So the "print(3)" is the expression to be run, even it's in 2nd position, which should be add argument.
Guest *dracodoc .* @ 2017-05-19 23:07:22 originally posted:
Out of curiosity, I checked further:
Find the C function name "do_onexit" by checking "on.exit" entry point in "$R HOME/src/main/names.c", then find the C code by searching "do_onexit", this looks to be the part that parse the arguments.
I didn't check further why this argument parse is getting weird result in this case, but we now know it's not just position match too.
In any case, "sys.on.exit()" can be helpful.
I guess this issue has been recently solved in R 4.0.2: wch/r-source@5bd6e3c
Originally posted on 2020-06-22 15:00:18
Sign in to join the discussion
Sign in with GitHub