32

Is it possible to remove an element from ... and pass ... onto other functions? My first two attempts failed:

parent = function(...)
{

   a = list(...)
   str(a)
   a$toRemove = NULL  
   str(a)

   # attempt 1   
   child(a)   

   # attempt 2
   child( ... = a )
}


child = function(...)
{
  a = list( ... )
  str(a)
}

parent( a = 1 , toRemove = 2 )

Edit
Sorry about the confusion. I fixed child(). The intent was to have child list the contents of ...

Edit2
Here's more of a real-world example (but still fairly simple so we can have a useful conversation about it). Parent is called via recursion. Parent need to know the depth of the recursive call. Callers outside of parent should't know about "depth" nor should they set it when calling parent(). Parent calls other functions, in this case child(). Child needs values in ... Clearly child doesn't need "depth" because parent generated it for its own use.

parent = function( ... )
{

   depth = list(...)$depth      
   if ( is.null( depth ) )
   {
       depth = 1
   }  
   print( depth )

   # parent needs value of depth to perform various calculations (not shown here)

   if ( depth == 5 )
   {
       return()
   }
   else
   {
      # child doesn't need "depth" in ...
      child( ... ) 
   }

   # yikes!  now we've added a second, third, etc. depth value to ...
   parent( depth = depth + 1 , ... )

}


child = function(...) 
{       
    # does some magic    
}
Aaron left Stack Overflow
  • 36,704
  • 7
  • 77
  • 142
Suraj
  • 35,905
  • 47
  • 139
  • 250

5 Answers5

32

One way to manipulate these things is to wrap the child function inside parent, and use a definition that puts any arguments you don't want passing on to child after the ... argument. For example:

parent <- function(...) {
    localChild <- function(..., toRemove) child(...)
    localChild(...)
}
child <- function(a) {
    a + 10
}

> parent(a = 1, toRemove = 10)
[1] 11

Another way is to use do.call():

parent2 <- function(...) {
    a <- list(...)
    a$toRemove <- NULL
    do.call(child2, a)
}
child2 <- function(b) {
    b + 10
}
> parent2(b = 1, toRemove = 10)
[1] 11

Depending on your actual use case, the do.call() is perhaps closest to what you intended with your Question.

Gavin Simpson
  • 170,508
  • 25
  • 396
  • 453
  • 1
    Gavin - if parent has some named parameters before the "...", how would I pass those in the do.call? Presumably I'd have to add those to "a"? – Suraj Aug 11 '11 at 16:16
  • 1
    Gavin - sorry...I meant to ask if there's some programmatic way to add other named parameters to "a". Obviously I could just add them manually, but if I add more parameter down the road I have to remember to update "a" – Suraj Aug 11 '11 at 16:22
  • 2
    @SFun28 Yep; Say `parent()` has arguments `x`, `y`, and `...` and `child(x, y, a)` but doesn't have `b`. Get rid of `b` as shown above (`dots <- list(...); dots$b <- NULL`), then do `do.call(child, c(dots, x = x, y = y)` for example. Like I said, which idiom I show will depend on what you *really* want to do. The `localFoo()` trick is used a lot in plotting code in base R to pass graphical parameters and related arguments down to other plotting code. I have a [blog post](http://ucfagls.wordpress.com/2011/07/23/passing-non-graphical-parameters-to-graphical-functions-using/) on this. – Gavin Simpson Aug 11 '11 at 16:22
  • 1
    Our Comments crossed in the ether. One way might be to use the `match.call()` idiom. E.g. `foo <- function(a, b, c, ...) as.list(match.call())[-1]` which returns a list, try it: `foo(a = 1, b = 2, c = 3, d = 4)` Then you eliminate the variable you don't want from that list and pass that to `do.call`. There are many ways to skin this cat - which is useful will depend heavily on context. Anyway, as coder, you have access to both `parent()` and `child()` if you update one you update the other. Simpler code is easier code to read/understand – Gavin Simpson Aug 11 '11 at 16:28
  • Gavin - I didn't know about match.call! Very cool. I think that's the better way to go for my purpose (the embedded function doesn't quite serve my needs and I like programmatic access to all args so I don't introduce bugs later when I add parameters). However, is there a memory concern? If a parameter is a large dataset, will match.call create a copy of the dataset? I want to say yes because R is a functional language, but I'm not sure. Perhaps match.call somehow maintains pointers to the original data? – Suraj Aug 11 '11 at 16:37
  • 2
    @SFun28 : see my answer then. And when using match.call, pay attention to the frame where eval() works. the lm() function uses this construct, but it can give rather strange results. – Joris Meys Aug 11 '11 at 16:38
  • Joris, Gavin - do you have a view on the memory issue I asked about above? Lots of messages flying around this post so might have been lost in the ether. – Suraj Aug 11 '11 at 17:02
  • @SFun28 By "memory" do you mean the point about forgetting to update something if one of the functions changes? Also, could you clarify what *exactly* you want to do. Is your Q to be taken literally - you want to spit out just a part of the arguments passed in as `...`? Or is it more general? This Q&A has become a bit if this, but if that, or if the other... Something concrete to focus the discussion would be great. – Gavin Simpson Aug 11 '11 at 17:46
  • Gavin - By the memory issue I mean the following: If I use the do.call/match.call() approach then doesn't as.list(match.call())[-1] duplicate all argument objects in memory? Imagine that one of the arguments is a large dataset. Now i've got two copies of it around (I think?). – Suraj Aug 11 '11 at 18:23
  • Also, I posted a more realistic example as to the issue I'm facing – Suraj Aug 11 '11 at 18:23
  • @SFun28 : I'm not sure, I'll check on that tomorrow. – Joris Meys Aug 11 '11 at 23:17
5

Your child function is erroneous. Try

> child(a=1)
Error in str(a) : object 'a' not found

edit : no longer applicable.

The ... argument should only be used to pass parameters to a next function. You cannot get the parameters from there that easily, unless you convert them to a list. So your child function could be :

child <- function(...)
{
  mc <- match.call()  # or mc <- list(...)
  str(mc$a)
}

Which doesn't make sense. You can't know whether the user specified a or not. The correct way would be to include a as an argument in your function. the ... is to pass arguments to the next one :

child <- function(a, ...){
    str(a,...)
}

Then you could do :

parent <- function(...){

   mc <- match.call()
   mc$toRemove <- NULL
   mc[[1L]] <- as.name("child")
   eval(mc)

}

or use the list(...) and do.call() construct @Gavin proposed. The benefit of match.call() is that you can include non-dot arguments as well. This allows your parent function to specify defaults for the child :

parent <- function(a=3, ...){
    ... (see above)
}
Joris Meys
  • 106,551
  • 31
  • 221
  • 263
  • Joris - fixed child function. I meant to convert ... to list and str that – Suraj Aug 11 '11 at 16:42
  • @SFun28 : I see. Well, that makes my example a bit odd, but I still leave it here per Gavin 's request to illustrate the use of match.call() – Joris Meys Aug 11 '11 at 16:44
  • Joris - yes, please leave it here. This is realy good info for the community. – Suraj Aug 11 '11 at 16:47
4

Here's an example of how to get the items out of ... and remove an element and then I call the next function with do.call:

parent <- function(...){
   funArgs <-  list(...)
   str(funArgs)
   ## remove the second item
   newArgs <- funArgs[-2]
   str(newArgs)
   ## if you want to call another function, use do.call
   do.call(child, newArgs)
  }

child = function(...)
{
  cat("Don't call me a child, buddy!\n")
  a <- list(...)
  str(a)
}


parent(a=1, b=2, c=3)

If you need to add more items to your arguments, as opposed to removing arguments, keep in mind that do.call likes named lists where the names are the argument names and the list values are the argument values. It's in the help file, but I struggled with that a bit before finally figuring it out.

JD Long
  • 59,675
  • 58
  • 202
  • 294
  • I don't think ist as complicated as that. Try just `funArgs <- list(...)` – IRTFM Aug 11 '11 at 15:31
  • The example in an Answer that should have been a comment is not a failing of `list(...)` it is calling `mean()` with a list as an argument. I'm not aware of situations where `list(...)` doesn't work. – Gavin Simpson Aug 11 '11 at 15:57
3

You're getting some good answers, but here's something simple that addresses your specific example:

parent = function(...)
{

   a = list(...)
   str(a)
   a$toRemove = NULL  
   str(a)

   # attempt 1   
   child(a)   

   # attempt 2
   #child(...)
}

child = function(...)
{
    a <- as.list(...)   
    str(a)
}

parent( a = 1 , toRemove = 2 )

which returns:

List of 2
 $ a       : num 1
 $ toRemove: num 2
List of 1
 $ a: num 1
List of 1
 $ a: num 1

Your original version was throwing an error, since a wasn't defined in child. Then simply using as.list(...) in child (as opposed to just list(...)) seems to generate the output you want. Note that I'm using your attempt 1 only here.

joran
  • 169,992
  • 32
  • 429
  • 468
  • joran - child shouldn't know about "a". the purpose of child was simply to list the "..." to test whether toRemove was removed from the ... that's input to parent – Suraj Aug 11 '11 at 16:18
  • sorry...realized my child function had an error. fixed. My intent wasn't to have a in the child function, it was to list to contents of ... – Suraj Aug 11 '11 at 16:43
  • @SFun28 My apologies, I must have misunderstood what you were looking for. – joran Aug 11 '11 at 16:43
  • @SFun28 Our messages crossed...that makes more sense! I'm doubtful my answer was what you were looking for, but given your edit it seems at least on topic enough that I won't delete it... – joran Aug 11 '11 at 16:45
  • yes...lets leave this answer here, will be useful for others searching on the topic – Suraj Aug 11 '11 at 16:48
0

I don't think the listed answers solve the problem, or at least not as I read it. Suppose you wanted to pass some parameters, like say 'xmax' and 'xmin' , to child(...) as actual variables?
in child's environment, it wants to see variables named 'xmax' and 'xmin', and the examples presented so far do not seem to make those variables available. Try inserting a line like

xmax-xmin -> xrange

into the child() function and it'll throw an error.
The whole point of the original question, I thought, was to allow passing a subset of the optional "..." variables to child() . You can tailor the solutions for simple cases, e.g. sum(...), where sum(unlist(the_modified_list)) works. In the more general case, I still can't see a solution. We probably need to elevate this problem to the R-help mailing list.

Edit: see the detailed presentation at http://ucfagls.wordpress.com/2011/07/23/passing-non-graphical-parameters-to-graphical-functions-using/

Carl Witthoft
  • 20,573
  • 9
  • 43
  • 73
  • Carl - the reason why we can't access xmin and xmax is that they are not explicitly specified in the parameter list. The purpose of ... is to say "there are some other parameters that I don't need, but I'm calling functions that might need them". If child needed xmax, it would explicitly list xmax as a parameter. Of course, child can access xmax within ... by converting ... to list – Suraj Aug 11 '11 at 16:57
  • Carl - Thanks for the link to my blog post. This describes the `localFoo()` idiom I mention in my Answer. One problem with this particular Q&A is that it wasn't clear that the OPs question was specific or general. I wrote my answer from a general point of view. I still don't really understand what @SFun28 *really* wants to do - need to read the comments again. – Gavin Simpson Aug 11 '11 at 17:43
  • SFun28: child may not *need* xmax, but it can't even access xmax until that variable is properly passed to it. My point is that you can't use an existing R-function as the "child" function because that function won't be set up to unlist the received variable. – Carl Witthoft Aug 11 '11 at 18:30
  • Link in the above goes to a "private" wordpress blog. – dash2 Jun 06 '20 at 17:58