Home Comments Thread
New Thread

1 Comment

giscus-bot giscus-bot 2022-12-17 01:23:18
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.

yihui yihui 2022-12-17 01:23:19

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

giscus-bot giscus-bot 2022-12-17 01:23:20
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).

giscus-bot giscus-bot 2022-12-17 01:23:21
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...

yihui yihui 2022-12-17 01:23:22

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

yihui yihui 2022-12-17 01:23:22

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

yihui yihui 2022-12-17 01:23:23

I see. That is very helpful. Thanks a lot!

Originally posted on 2017-05-19 20:47:01

giscus-bot giscus-bot 2022-12-17 01:23:24
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

giscus-bot giscus-bot 2022-12-17 01:23:25
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:

  1. explicit assignment expression using "<-" works as expected.
  2. if you wrap "=" with {} so it will not be confused as an named argument, it's also assignment expression.
  3. 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.
  4. using "=" without {} in on.exit have another interpretation approach, probably related to the C code.
yihui yihui 2022-12-17 01:23:26

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

yihui yihui 2022-12-17 01:23:27

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

giscus-bot giscus-bot 2022-12-17 01:23:28
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.

giscus-bot giscus-bot 2022-12-17 01:23:28
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.

https://github.com/wch/r-source/blob/7e8eb6a01df2eca22b7600d3d35916d80031aedc/src/main/builtin.c#L143

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.

yihui yihui 2022-12-17 01:23:30

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