9
votes

In R there's common function calling pattern that looks like this:

child = function(a, b, c) {
  a * b - c
}

parent = function(a, b, c) {
  result = child(a=a, b=b, c=c)
}

This repetition of names is helpful because it prevents potentially insidious errors if the ordering of the child's arguments were to change, or if an additional variable were to be added into the list:

childReordered = function(c, b, a) {  # same args in different order
  a * b - c
}

parent = function(a, b, c) {
  result = childReordered(a, b, c)  # probably an error
}

But this becomes cumbersome when the names of the arguments get long:

child = function(longVariableNameA, longVariableNameB, longVariableNameC) {
 longVariableNameA * longVariableNameB - longVariableNameC
}

parent = function(longVariableNameA, longVariableNameB, longVariableNameC) {
  child(longVariableNameA=longVariableNameA, longVariableNameB=longVariableNameB, longVariableNameC=longVariableNameB)
}

I'd like to have a way to get the safety of the named parameters without needing to actually type the names again. And I'd like to be able to do this when I can modify only the parent, and not the child. I'd envision something like this:

parent = function(a, b, c) {
  result = child(params(a, b, c))
}

Where params() would be a new function that converts the unnamed arguments to named parameters based on the names of the variables. For example:

child(params(c,b,a)) == child(a=a, b=b, c=c)

There are a couple function in 'pryr' that come close to this, but I haven't figured out how to combine them to do quite what I want. named_dots(c,b,a) returns list(c=c, b=b, a=a), and standardise_call() has a similar operation, but I haven't figured out how to be able to convert the results into something that can be passed to an unmodified child().

I'd like to be able to use a mixture of implicit and explicitly named parameters:

child(params(b=3, c=a, a)) == child(b=3, c=a, a=a)

It would also be nice to be able to mix in some unnamed constants (not variables), and have them treated as named arguments when passed to the child.

child(params(7, c, b=x)) == child(a=7, c=c, b=x)  # desired
named_dots(7, c, b=x) == list("7"=7, c=c, b=x)  # undesired

But in a non-R way, I'd prefer to raise errors rather than trying to muddle through with what are likely programmer mistakes:

child(params(c, 7, b=x)) # ERROR: unnamed parameters must be first

Are there tools that already exist to do this? Simple ways to piece together existing functions to do what I want? Better ways to accomplish the same goal of getting safety in the presence of changing parameter lists without unwieldy repetition? Improvements to my suggested syntax to make it even safer?

Pre-bounty clarification: Both the parent() and child() functions should be considered unchangeable. I'm not interested in wrapping either with a different interface. Rather, I'm looking here for a way to write the proposed params() function in a general manner that can rewrite the list of arguments on the fly so that both parent() and child() can be used directly with a safe but non-verbose syntax.

Post-bounty clarification: While inverting the parent-child relationship and using do.call() is a useful technique, it's not the one I'm looking for here. Instead, I'm looking for a way to accept a '...' argument, modify it to have named parameters, and then return it in a form that the enclosing function will accept. It's possible that as others suggest this is truly impossible. Personally, I currently think it is possible with a C level extension, and my hope is that this extension already exists. Perhaps the vadr package does what I want? https://github.com/crowding/vadr#dot-dot-dot-lists-and-missing-values

Partial credit: I feel silly just letting the bounty expire. If there are no full solutions, I'll award it to anyone who gives a proof of concept of at least one of the necessary steps. For example, modifying a '...' argument within a function and then passing it to another function without using do.call(). Or returning an unmodified '...' argument in a way that the parent can use it. Or anything that best points the way toward a direct solution, or even some useful links: http://r.789695.n4.nabble.com/internal-manipulation-of-td4682090.html But I'm reluctant to award it to an answer that starts with the (otherwise entirely reasonable) premise that "you don't want to do that", or "that's impossible so here's an alternative".

Bounty awarded: There are several really useful and practical answers, but I chose to award the bounty to @crowding. While he (probably correctly) asserts that what I want is impossible, I think his answer comes closest to the 'idealistic' approach I'm aiming for. I also think that his vadr package might be a good starting point for a solution, whether it matches my (potentially unrealistic) design goals or not. The 'accepted answer' is still up for grabs if in case someone figures out a way to do the impossible. Thanks for the other answers and suggestions, and hopefully they will help someone put together the pieces for a more robust R syntax.

6
do.call and match.call don't do all you want, but I think having a closer look at these functions might be interesting for you. By the way, your question reminds my of a question I asked some time ago, unfortunately unanswered. - CL.
It would help if you provided some test case to verify possible solutions. - MrFlick
A solution would be any function params() such that child(params(c,b,a)) always evaluates identically to child(c=c, b=b, a=a). Both you and mnel offer practical solutions using wrappers, which hopefully will be useful to others who are able to to use do.call() syntax. But for the purposes of this question and the bounty, I'm looking only for a way to write a function that accepts a multiparameter '...' argument, modifies it to have named parameters, and returns it in a way that a parent function will accept this single argument in lieu of the multiple arguments it would normally receive. - Nathan Kurz
@NathanKurz I can get your last comment, you'll still have to modify each call to those functions to add the new one inside, so refactoring to use the wrapper is not more complicated than what you ask for... (and the do.call method does not invert the relationship) - Tensibai
Curious how @Mnel's answer, and my expounding thereon, fail to address your question. I guess it could be the "both parent and child should be considered unchangeable", but in your own example you change parent by using child(params(...)). I see this as a change of the same magnitude as say, params(child) which is basically what mnel's / my answer do. - BrodieG

6 Answers

6
votes

I think attempting to overwrite the built argument matching functionality of R is somewhat dangerous, so here is a solution that uses do.call.

It was unclear how much of parent is changeable

# This ensures that only named arguments to formals get passed through
parent = function(a, b, c) {
   do.call("child", mget(names(formals(child))))
}

A second option, based on the "magic" of write.csv

# this second option replaces the call to parent with child and passes the 
# named arguments that have been matched within the call to parent
# 

parent2 <- function(a,b,c){
  Call <- match.call()
  Call[[1]] <- quote(child)
  eval(Call)
}
4
votes

You can't change the parameters to a function from inside the function call. The next best way would be to write a simple wrapper around the call. Perhaps something like this can help

with_params <- function(f, ...) {
    dots <- substitute(...())
    dots <- setNames(dots, sapply(dots, deparse))
    do.call(f, as.list(dots), envir=parent.frame())
}

And we can test with something like

parent1 <- function(a, b, c) {
  child(a, b, c)
}

parent2 <- function(a, b, c) {
  with_params(child, a, b, c)
}

child <- function(a, b, c) {
  a * b - c
}

parent1(5,6,7)
# [1] 23
parent2(5,6,7)
# [1] 23

child <- function(c, a, b) {
  a * b - c
}
parent1(5,6,7)
# [1] 37
parent2(5,6,7)
# [1] 23

Note that parent2 is robust to change in the parameter order of the child while parent is.

4
votes

It's not easy to get the exact syntax you're proposing. R is lazily evaluated, so syntax appearing in an argument to a function is only looked at after the function call is started. By the time the interpreter encounters params() it will have already started the call to child() and bound all of child's arguments and executed some of child's code. Can't rewrite child's arguments after the horse has left the barn, so to speak.

(Most non-lazy languages wouldn't let you do this either, for various reasons)

So the syntax will need to be something that that has the the references to 'child' and 'params' in its arguments. vadr has a %()% operator that can work. It applies a dotlist given on the right to a function given on the left. So you would write:

child %()% params(a, b, c)

where params catches its arguments in a dotlist and manipulates them:

params <- function(...) {
  d <- dots(...)
  ex <- expressions(d)
  nm <- names(d) %||% rep("", length(d))
  for (i in 1:length(d)) {
    if (is.name(ex[[i]]) && nm[[i]] == "") {
      nm[[i]] = as.character(ex[[i]])
    }
  }
  names(d) <- nm
  d
}
1
votes

This was meant to be a comment but it doesn't fit the limits. I commend you for your programming ambition and purity, but I think the goal is unattainable. Let's assume params exists and apply it to the function list. By the definition of params, identical(list(a = a, b = b, c = c) , list(params(a, b, c)). From this it follows that identical(a, params(a, b, c)) by taking the first element of the first and second argument of identical. From which it follows that params does not depend on its second and later arguments, a contradiction. Q.E.D. But I think your idea is a lovely example of DRY in R and I am perfectly happy with do.call(f, params(a,b,c)), which has an additional do.call, but no repetition. With your permission I would like to incorporate it in my package bettR which collects various ideas to improve the R language. A related idea which I was toying with is creating a function that allows another function to get missing args from the calling frame. That is, instead of calling f(a = a, b = b), one could call f() and inside f there would be something like args = eval(formals(f), parent.frame()) but encapsulated into a macro-like construct args = get.args.by.name or some such. This is distinct from your idea in that it requires f to be programmed deliberately to have this feature.

1
votes

Here's an answer that at first would appear to work. Diagnosing why it does not may lead to enlightenment as to why it cannot (hint: see first paragraph of @crowding's answer).

params<-function(...) {
  dots<-list(...)
  names(dots)<-eval(substitute(alist(...)))
  child.env<-sys.frame(-1)  
  child.fun<-sys.function(sys.parent()+1)
  args<-names(formals(child.fun))
  for(arg in args) {
    assign(arg,dots[[arg]],envir=child.env)
  }
  dots[[args[[1]]]]
}

child1<-function(a,b,c) a*b-c
parent1<-function(a,b,c) child1(params(a,b,c))
parent1(1,2,3)
#> -1

child2<-function(a,c,b) a*b-c #swap b and c in formals
parent2<-function(a,b,c) child2(params(a,b,c)) #mirrors parent1
parent2(1,2,3)
#> -1

Both produce 1*2-3 == -1 even though the order of b=2 and c=3 have been swapped in the formal argument list of child1 versus child2.

1
votes

This is basically a clarification to Mnel's answer. If it happens to answer your question, please do not accept it or award the bounty; it should go to Mnel. First, we define call_as_parent, which you use to call a function inside another function as the outside function:

call_as_parent <- function(fun) {
  par.call <- sys.call(sys.parent())
  new.call <- match.call(
    eval(par.call[[1L]], parent.frame(2L)), 
    call=par.call
  )
  new.call[[1L]] <- fun
  eval(new.call, parent.frame())
}

Then we define parent and child:

child <- function(c, b, a) a - b - c
parent <- function(a, b, c) call_as_parent(child)

And finally, some examples

child(5, 10, 20)
# [1] 5
parent(5, 10, 20)
# [1] -25

Notice how clearly in the second example the 20 is getting matched to c, as it should.