R: Understanding how ellipsis (…) works in nested functions, & how iit doesn't
up vote
1
down vote
favorite
This is how I have been thinking that ellipsis works in nested functions: when you hand a set of arguments to a function via ellipsis, any function subsidiary to it on the call stack can get at those arguments -- I thought, via its own ellipsis. I have believed that arguments passed to ellipsis cumulate, so that the inmost ellipsis contains all the arguments passed through ellipsis arguments in any of the functions superior to it in the call stack.
But I just did an experiment to confirm this, and it now appears to me to be wrong. Thus:
> f02 <- function(...){
+ vv <- list(...)
+ print(vv)
+ }
> f01 <- function(...){
+ f02(b = 2)
+ }
> f01(a=1)
$`b`
[1] 2
Here the inner ellipsis does not seem to have inherited the a=1
argument from the outer ellipsis.
So my current theory is that when you take an action that asks for the contents of …
, such as list(…)
, match.call(expand.dots=TRUE)
, or as.list(substitute(list(...)))[-1]
, you only get the first instance of …
that is encountered, based on the search path under normal scoping rules. But I have to say, this seems unlikely to me. If it were true, then, e.g., graphical parameters supplied to a plotting function several calls down would suffer mysterious failures if one of the intervening functions had a …
argument.
So I am wondering if there are some special rules for scoping arguments sought in dot-dot-dots, such as looking for a superior instance if the local one is empty, or if you look in …
for a particular named argument, say list(...)$my_parameter
, and do not find it there . Neither of these solutions strikes me as very plausible, but, well, none of the ones I have come up with do.
Previous questions on this topic seem to be focused mainly on various edge cases. I'm looking more for understanding the passing rules in the normal case (but possibly with multiple layers of calls).
r parameter-passing callstack
add a comment |
up vote
1
down vote
favorite
This is how I have been thinking that ellipsis works in nested functions: when you hand a set of arguments to a function via ellipsis, any function subsidiary to it on the call stack can get at those arguments -- I thought, via its own ellipsis. I have believed that arguments passed to ellipsis cumulate, so that the inmost ellipsis contains all the arguments passed through ellipsis arguments in any of the functions superior to it in the call stack.
But I just did an experiment to confirm this, and it now appears to me to be wrong. Thus:
> f02 <- function(...){
+ vv <- list(...)
+ print(vv)
+ }
> f01 <- function(...){
+ f02(b = 2)
+ }
> f01(a=1)
$`b`
[1] 2
Here the inner ellipsis does not seem to have inherited the a=1
argument from the outer ellipsis.
So my current theory is that when you take an action that asks for the contents of …
, such as list(…)
, match.call(expand.dots=TRUE)
, or as.list(substitute(list(...)))[-1]
, you only get the first instance of …
that is encountered, based on the search path under normal scoping rules. But I have to say, this seems unlikely to me. If it were true, then, e.g., graphical parameters supplied to a plotting function several calls down would suffer mysterious failures if one of the intervening functions had a …
argument.
So I am wondering if there are some special rules for scoping arguments sought in dot-dot-dots, such as looking for a superior instance if the local one is empty, or if you look in …
for a particular named argument, say list(...)$my_parameter
, and do not find it there . Neither of these solutions strikes me as very plausible, but, well, none of the ones I have come up with do.
Previous questions on this topic seem to be focused mainly on various edge cases. I'm looking more for understanding the passing rules in the normal case (but possibly with multiple layers of calls).
r parameter-passing callstack
add a comment |
up vote
1
down vote
favorite
up vote
1
down vote
favorite
This is how I have been thinking that ellipsis works in nested functions: when you hand a set of arguments to a function via ellipsis, any function subsidiary to it on the call stack can get at those arguments -- I thought, via its own ellipsis. I have believed that arguments passed to ellipsis cumulate, so that the inmost ellipsis contains all the arguments passed through ellipsis arguments in any of the functions superior to it in the call stack.
But I just did an experiment to confirm this, and it now appears to me to be wrong. Thus:
> f02 <- function(...){
+ vv <- list(...)
+ print(vv)
+ }
> f01 <- function(...){
+ f02(b = 2)
+ }
> f01(a=1)
$`b`
[1] 2
Here the inner ellipsis does not seem to have inherited the a=1
argument from the outer ellipsis.
So my current theory is that when you take an action that asks for the contents of …
, such as list(…)
, match.call(expand.dots=TRUE)
, or as.list(substitute(list(...)))[-1]
, you only get the first instance of …
that is encountered, based on the search path under normal scoping rules. But I have to say, this seems unlikely to me. If it were true, then, e.g., graphical parameters supplied to a plotting function several calls down would suffer mysterious failures if one of the intervening functions had a …
argument.
So I am wondering if there are some special rules for scoping arguments sought in dot-dot-dots, such as looking for a superior instance if the local one is empty, or if you look in …
for a particular named argument, say list(...)$my_parameter
, and do not find it there . Neither of these solutions strikes me as very plausible, but, well, none of the ones I have come up with do.
Previous questions on this topic seem to be focused mainly on various edge cases. I'm looking more for understanding the passing rules in the normal case (but possibly with multiple layers of calls).
r parameter-passing callstack
This is how I have been thinking that ellipsis works in nested functions: when you hand a set of arguments to a function via ellipsis, any function subsidiary to it on the call stack can get at those arguments -- I thought, via its own ellipsis. I have believed that arguments passed to ellipsis cumulate, so that the inmost ellipsis contains all the arguments passed through ellipsis arguments in any of the functions superior to it in the call stack.
But I just did an experiment to confirm this, and it now appears to me to be wrong. Thus:
> f02 <- function(...){
+ vv <- list(...)
+ print(vv)
+ }
> f01 <- function(...){
+ f02(b = 2)
+ }
> f01(a=1)
$`b`
[1] 2
Here the inner ellipsis does not seem to have inherited the a=1
argument from the outer ellipsis.
So my current theory is that when you take an action that asks for the contents of …
, such as list(…)
, match.call(expand.dots=TRUE)
, or as.list(substitute(list(...)))[-1]
, you only get the first instance of …
that is encountered, based on the search path under normal scoping rules. But I have to say, this seems unlikely to me. If it were true, then, e.g., graphical parameters supplied to a plotting function several calls down would suffer mysterious failures if one of the intervening functions had a …
argument.
So I am wondering if there are some special rules for scoping arguments sought in dot-dot-dots, such as looking for a superior instance if the local one is empty, or if you look in …
for a particular named argument, say list(...)$my_parameter
, and do not find it there . Neither of these solutions strikes me as very plausible, but, well, none of the ones I have come up with do.
Previous questions on this topic seem to be focused mainly on various edge cases. I'm looking more for understanding the passing rules in the normal case (but possibly with multiple layers of calls).
r parameter-passing callstack
r parameter-passing callstack
asked Nov 21 at 17:41
andrewH
7831716
7831716
add a comment |
add a comment |
2 Answers
2
active
oldest
votes
up vote
5
down vote
accepted
Ellipsis has to be explicitly provided to pass to a nested function, so for example, in your f02, the list
call gets whatever was passed to f02 as its own arguments. In contrast, in f01, the arguments are simply ignored. You could pass the arguments to f02 within f01 thus:
f01 <- function(...){
f02(b = 2,...)
}
Result:
f01(a=1)
$b
[1] 2
$a
[1] 1
This works regardless of the number of arguments in the ellipsis:
f01(a=1,c=3)
$b
[1] 2
$a
[1] 1
$c
[1] 3
add a comment |
up vote
0
down vote
I'm hoping that the forum lets me post an answer and still accept iod's answer. This is my intent.
It appears that my original belief -- that the dots cumulate all the arguments supplied to them along the call stack -- is true provided that ellipsis (...
) are included in both the function definitions and the function calls for all the calls subsidiary to the first. See the code below. To me this suggests that a ... argument should routinely be added to function calls embedded inside other functions, if there is any possibility that subsidiary functions somewhere down the chain may need an argument supplied at the top level which can not be identified and passed explicitly when the function is written. If someone reading this has a good reason to believe this is a bad idea, I'd like to hear about it.
Note that although ellipsis are required in the calls to functions 2 and 3 in order to pass their arguments inward, it is forbidden at the top level, resulting in a '...' used in an incorrect context
error. I find this puzzling. I would have expected a '...' not found
error, given that the dots are allowed in the subsidiary calls.
In addition, I don't understand why the name of the third argument is reported as `c`. It is not because c
is a function name; the same thing happens with c1
.
> f03 <- function(...){
+ vv <- list(...)
+ print(vv)
+ }
> f02 <- function(...){
+ f03(c = 3, ...)
+ }
> f01 <- function(...){
+ f02(b = 2, ...)
+ }
> f01(a = 1)
$`c`
[1] 3
$b
[1] 2
$a
[1] 1
Not sure what's going on there, but when I run your code, the c is reported as a c, without the ticks. Re why but to include ...: sometimes you want a lower level function to return a certain type of value. If you allow ..., you may accidentally cause a change in some default setting (consider apply and simplify=TRUE). Moreover, the expectation should not be that the user know what the lower level functions even are. Unless explicitly stated in the documentation, the user can't know what the ellipsis is being passed to, so your opening yourself up to unexpected errors by passing random ...s
– iod
Nov 29 at 1:11
add a comment |
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
5
down vote
accepted
Ellipsis has to be explicitly provided to pass to a nested function, so for example, in your f02, the list
call gets whatever was passed to f02 as its own arguments. In contrast, in f01, the arguments are simply ignored. You could pass the arguments to f02 within f01 thus:
f01 <- function(...){
f02(b = 2,...)
}
Result:
f01(a=1)
$b
[1] 2
$a
[1] 1
This works regardless of the number of arguments in the ellipsis:
f01(a=1,c=3)
$b
[1] 2
$a
[1] 1
$c
[1] 3
add a comment |
up vote
5
down vote
accepted
Ellipsis has to be explicitly provided to pass to a nested function, so for example, in your f02, the list
call gets whatever was passed to f02 as its own arguments. In contrast, in f01, the arguments are simply ignored. You could pass the arguments to f02 within f01 thus:
f01 <- function(...){
f02(b = 2,...)
}
Result:
f01(a=1)
$b
[1] 2
$a
[1] 1
This works regardless of the number of arguments in the ellipsis:
f01(a=1,c=3)
$b
[1] 2
$a
[1] 1
$c
[1] 3
add a comment |
up vote
5
down vote
accepted
up vote
5
down vote
accepted
Ellipsis has to be explicitly provided to pass to a nested function, so for example, in your f02, the list
call gets whatever was passed to f02 as its own arguments. In contrast, in f01, the arguments are simply ignored. You could pass the arguments to f02 within f01 thus:
f01 <- function(...){
f02(b = 2,...)
}
Result:
f01(a=1)
$b
[1] 2
$a
[1] 1
This works regardless of the number of arguments in the ellipsis:
f01(a=1,c=3)
$b
[1] 2
$a
[1] 1
$c
[1] 3
Ellipsis has to be explicitly provided to pass to a nested function, so for example, in your f02, the list
call gets whatever was passed to f02 as its own arguments. In contrast, in f01, the arguments are simply ignored. You could pass the arguments to f02 within f01 thus:
f01 <- function(...){
f02(b = 2,...)
}
Result:
f01(a=1)
$b
[1] 2
$a
[1] 1
This works regardless of the number of arguments in the ellipsis:
f01(a=1,c=3)
$b
[1] 2
$a
[1] 1
$c
[1] 3
answered Nov 21 at 17:47
iod
3,3641620
3,3641620
add a comment |
add a comment |
up vote
0
down vote
I'm hoping that the forum lets me post an answer and still accept iod's answer. This is my intent.
It appears that my original belief -- that the dots cumulate all the arguments supplied to them along the call stack -- is true provided that ellipsis (...
) are included in both the function definitions and the function calls for all the calls subsidiary to the first. See the code below. To me this suggests that a ... argument should routinely be added to function calls embedded inside other functions, if there is any possibility that subsidiary functions somewhere down the chain may need an argument supplied at the top level which can not be identified and passed explicitly when the function is written. If someone reading this has a good reason to believe this is a bad idea, I'd like to hear about it.
Note that although ellipsis are required in the calls to functions 2 and 3 in order to pass their arguments inward, it is forbidden at the top level, resulting in a '...' used in an incorrect context
error. I find this puzzling. I would have expected a '...' not found
error, given that the dots are allowed in the subsidiary calls.
In addition, I don't understand why the name of the third argument is reported as `c`. It is not because c
is a function name; the same thing happens with c1
.
> f03 <- function(...){
+ vv <- list(...)
+ print(vv)
+ }
> f02 <- function(...){
+ f03(c = 3, ...)
+ }
> f01 <- function(...){
+ f02(b = 2, ...)
+ }
> f01(a = 1)
$`c`
[1] 3
$b
[1] 2
$a
[1] 1
Not sure what's going on there, but when I run your code, the c is reported as a c, without the ticks. Re why but to include ...: sometimes you want a lower level function to return a certain type of value. If you allow ..., you may accidentally cause a change in some default setting (consider apply and simplify=TRUE). Moreover, the expectation should not be that the user know what the lower level functions even are. Unless explicitly stated in the documentation, the user can't know what the ellipsis is being passed to, so your opening yourself up to unexpected errors by passing random ...s
– iod
Nov 29 at 1:11
add a comment |
up vote
0
down vote
I'm hoping that the forum lets me post an answer and still accept iod's answer. This is my intent.
It appears that my original belief -- that the dots cumulate all the arguments supplied to them along the call stack -- is true provided that ellipsis (...
) are included in both the function definitions and the function calls for all the calls subsidiary to the first. See the code below. To me this suggests that a ... argument should routinely be added to function calls embedded inside other functions, if there is any possibility that subsidiary functions somewhere down the chain may need an argument supplied at the top level which can not be identified and passed explicitly when the function is written. If someone reading this has a good reason to believe this is a bad idea, I'd like to hear about it.
Note that although ellipsis are required in the calls to functions 2 and 3 in order to pass their arguments inward, it is forbidden at the top level, resulting in a '...' used in an incorrect context
error. I find this puzzling. I would have expected a '...' not found
error, given that the dots are allowed in the subsidiary calls.
In addition, I don't understand why the name of the third argument is reported as `c`. It is not because c
is a function name; the same thing happens with c1
.
> f03 <- function(...){
+ vv <- list(...)
+ print(vv)
+ }
> f02 <- function(...){
+ f03(c = 3, ...)
+ }
> f01 <- function(...){
+ f02(b = 2, ...)
+ }
> f01(a = 1)
$`c`
[1] 3
$b
[1] 2
$a
[1] 1
Not sure what's going on there, but when I run your code, the c is reported as a c, without the ticks. Re why but to include ...: sometimes you want a lower level function to return a certain type of value. If you allow ..., you may accidentally cause a change in some default setting (consider apply and simplify=TRUE). Moreover, the expectation should not be that the user know what the lower level functions even are. Unless explicitly stated in the documentation, the user can't know what the ellipsis is being passed to, so your opening yourself up to unexpected errors by passing random ...s
– iod
Nov 29 at 1:11
add a comment |
up vote
0
down vote
up vote
0
down vote
I'm hoping that the forum lets me post an answer and still accept iod's answer. This is my intent.
It appears that my original belief -- that the dots cumulate all the arguments supplied to them along the call stack -- is true provided that ellipsis (...
) are included in both the function definitions and the function calls for all the calls subsidiary to the first. See the code below. To me this suggests that a ... argument should routinely be added to function calls embedded inside other functions, if there is any possibility that subsidiary functions somewhere down the chain may need an argument supplied at the top level which can not be identified and passed explicitly when the function is written. If someone reading this has a good reason to believe this is a bad idea, I'd like to hear about it.
Note that although ellipsis are required in the calls to functions 2 and 3 in order to pass their arguments inward, it is forbidden at the top level, resulting in a '...' used in an incorrect context
error. I find this puzzling. I would have expected a '...' not found
error, given that the dots are allowed in the subsidiary calls.
In addition, I don't understand why the name of the third argument is reported as `c`. It is not because c
is a function name; the same thing happens with c1
.
> f03 <- function(...){
+ vv <- list(...)
+ print(vv)
+ }
> f02 <- function(...){
+ f03(c = 3, ...)
+ }
> f01 <- function(...){
+ f02(b = 2, ...)
+ }
> f01(a = 1)
$`c`
[1] 3
$b
[1] 2
$a
[1] 1
I'm hoping that the forum lets me post an answer and still accept iod's answer. This is my intent.
It appears that my original belief -- that the dots cumulate all the arguments supplied to them along the call stack -- is true provided that ellipsis (...
) are included in both the function definitions and the function calls for all the calls subsidiary to the first. See the code below. To me this suggests that a ... argument should routinely be added to function calls embedded inside other functions, if there is any possibility that subsidiary functions somewhere down the chain may need an argument supplied at the top level which can not be identified and passed explicitly when the function is written. If someone reading this has a good reason to believe this is a bad idea, I'd like to hear about it.
Note that although ellipsis are required in the calls to functions 2 and 3 in order to pass their arguments inward, it is forbidden at the top level, resulting in a '...' used in an incorrect context
error. I find this puzzling. I would have expected a '...' not found
error, given that the dots are allowed in the subsidiary calls.
In addition, I don't understand why the name of the third argument is reported as `c`. It is not because c
is a function name; the same thing happens with c1
.
> f03 <- function(...){
+ vv <- list(...)
+ print(vv)
+ }
> f02 <- function(...){
+ f03(c = 3, ...)
+ }
> f01 <- function(...){
+ f02(b = 2, ...)
+ }
> f01(a = 1)
$`c`
[1] 3
$b
[1] 2
$a
[1] 1
answered Nov 29 at 0:50
andrewH
7831716
7831716
Not sure what's going on there, but when I run your code, the c is reported as a c, without the ticks. Re why but to include ...: sometimes you want a lower level function to return a certain type of value. If you allow ..., you may accidentally cause a change in some default setting (consider apply and simplify=TRUE). Moreover, the expectation should not be that the user know what the lower level functions even are. Unless explicitly stated in the documentation, the user can't know what the ellipsis is being passed to, so your opening yourself up to unexpected errors by passing random ...s
– iod
Nov 29 at 1:11
add a comment |
Not sure what's going on there, but when I run your code, the c is reported as a c, without the ticks. Re why but to include ...: sometimes you want a lower level function to return a certain type of value. If you allow ..., you may accidentally cause a change in some default setting (consider apply and simplify=TRUE). Moreover, the expectation should not be that the user know what the lower level functions even are. Unless explicitly stated in the documentation, the user can't know what the ellipsis is being passed to, so your opening yourself up to unexpected errors by passing random ...s
– iod
Nov 29 at 1:11
Not sure what's going on there, but when I run your code, the c is reported as a c, without the ticks. Re why but to include ...: sometimes you want a lower level function to return a certain type of value. If you allow ..., you may accidentally cause a change in some default setting (consider apply and simplify=TRUE). Moreover, the expectation should not be that the user know what the lower level functions even are. Unless explicitly stated in the documentation, the user can't know what the ellipsis is being passed to, so your opening yourself up to unexpected errors by passing random ...s
– iod
Nov 29 at 1:11
Not sure what's going on there, but when I run your code, the c is reported as a c, without the ticks. Re why but to include ...: sometimes you want a lower level function to return a certain type of value. If you allow ..., you may accidentally cause a change in some default setting (consider apply and simplify=TRUE). Moreover, the expectation should not be that the user know what the lower level functions even are. Unless explicitly stated in the documentation, the user can't know what the ellipsis is being passed to, so your opening yourself up to unexpected errors by passing random ...s
– iod
Nov 29 at 1:11
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53417788%2fr-understanding-how-ellipsis-works-in-nested-functions-how-iit-doesnt%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown