R: Understanding how ellipsis (…) works in nested functions, & how iit doesn't












1














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).










share|improve this question



























    1














    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).










    share|improve this question

























      1












      1








      1







      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).










      share|improve this question













      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






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked Nov 21 '18 at 17:41









      andrewH

      7811717




      7811717
























          2 Answers
          2






          active

          oldest

          votes


















          5














          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





          share|improve this answer





























            0














            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





            share|improve this answer





















            • 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 '18 at 1:11











            Your Answer






            StackExchange.ifUsing("editor", function () {
            StackExchange.using("externalEditor", function () {
            StackExchange.using("snippets", function () {
            StackExchange.snippets.init();
            });
            });
            }, "code-snippets");

            StackExchange.ready(function() {
            var channelOptions = {
            tags: "".split(" "),
            id: "1"
            };
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function() {
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled) {
            StackExchange.using("snippets", function() {
            createEditor();
            });
            }
            else {
            createEditor();
            }
            });

            function createEditor() {
            StackExchange.prepareEditor({
            heartbeatType: 'answer',
            autoActivateHeartbeat: false,
            convertImagesToLinks: true,
            noModals: true,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: 10,
            bindNavPrevention: true,
            postfix: "",
            imageUploader: {
            brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
            contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
            allowUrls: true
            },
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            });


            }
            });














            draft saved

            draft discarded


















            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

























            2 Answers
            2






            active

            oldest

            votes








            2 Answers
            2






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes









            5














            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





            share|improve this answer


























              5














              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





              share|improve this answer
























                5












                5








                5






                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





                share|improve this answer












                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






                share|improve this answer












                share|improve this answer



                share|improve this answer










                answered Nov 21 '18 at 17:47









                iod

                3,5692722




                3,5692722

























                    0














                    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





                    share|improve this answer





















                    • 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 '18 at 1:11
















                    0














                    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





                    share|improve this answer





















                    • 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 '18 at 1:11














                    0












                    0








                    0






                    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





                    share|improve this answer












                    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






                    share|improve this answer












                    share|improve this answer



                    share|improve this answer










                    answered Nov 29 '18 at 0:50









                    andrewH

                    7811717




                    7811717












                    • 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 '18 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 '18 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 '18 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 '18 at 1:11


















                    draft saved

                    draft discarded




















































                    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.




                    draft saved


                    draft discarded














                    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





















































                    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







                    Popular posts from this blog

                    404 Error Contact Form 7 ajax form submitting

                    How to know if a Active Directory user can login interactively

                    TypeError: fit_transform() missing 1 required positional argument: 'X'