4

Following on from How to create an R function programmatically?

I want to build R functions from their components like this:

testfn <- function(..., expression){
  args <- substitute(alist(...))
  body <- substitute(expression)
  eval(call("function", as.pairlist(eval(args)), body), parent.frame())
}

This works fine if there are default values:

testfn(x = 4, y = 5, expression = {
  y <- y + 2
  x + y
})

=>
function (x = 4, y = 5) 
{
    y <- y + 2
    x + y
}

But it will not work if there is no default argument for one or more of the args:

testfn(x = 4, y, expression = {
  y <- y + 2
  x + y
})

=>
 Error in eval(expr, envir, enclos) : 
 invalid formal argument list for "function" 

I can put = in the arg list to get this to work:

testfn(x = 4, y = , expression = {
  y <- y + 2
  x + y
})

=>
function (x = 4, y) 
{
    y <- y + 2
    x + y
}

But I don't want to enforce this in the function arguments. I have tried editing the alist in the function, but when I do this, I get another invalid formal argument list error:

testfn2 <- function(..., expression){
  args <- substitute(alist(...))
  body <- substitute(expression)
  for (arg in 2:length(args)){
    if(names(myargs)[arg] == ""){
       args[[arg]] <- as.name(paste(as.character(args)[arg], "="))
    }
  }
  eval(call("function", as.pairlist(eval(args)), body), parent.frame())
}

testfn2(x = 4, y, expression = {
  y <- y + 2
  x + y
})

=>
Error in eval(expr, envir, enclos) : 
invalid formal argument list for "function" 

How can I change this so I can call testfn() with missing argument defaults? I thought of constructing a new alist from a character string using parse but this will mean I cannot have e.g. functions as arguments.

Community
  • 1
  • 1
dspringate
  • 1,805
  • 2
  • 13
  • 20

3 Answers3

2

Here is one not very elegant way to do it :

testfn <- function(..., expression){
  args <- eval(substitute(alist(...)))
  body <- substitute(expression)

  ## Fix for missing default values
  tmp <- names(args)
  if (is.null(tmp)) tmp <- rep("", length(args))
  names(args)[tmp==""] <- args[tmp==""]
  args[tmp==""] <- list(NULL)

  eval(call("function", as.pairlist(args), body), parent.frame())
}
juba
  • 47,631
  • 14
  • 113
  • 118
  • Thanks @juba. That is nearly there, but it means that the default for y is set to `NULL`, which is slightly different because you could call the returned function with just the x argument without it throwing an error. – dspringate Jun 05 '15 at 11:19
  • @dspringate Sorry, I'm not sure I understand what you mean. If no default value is provided to `y`, you want the generated function to throw an error ? – juba Jun 05 '15 at 12:33
  • No, I mean that I want the returned function to be (in this example) function (x = 4, y) { y <- y + 2 x + y }, rather than function (x = 4, y = NULL) { y <- y + 2 x + y }, so that if that function is called it will return a missing without default error if y is not provided – dspringate Jun 05 '15 at 12:41
0

If I understand what you want to do, passing a zero-length symbol as the default value for y will do the trick:

> body <- expression(x + y)
> 
> args <- list()
> args[["y"]] <- alist(y=)$y #zero-length symbol
> args[["x"]] <- 4

> eval(call("function", as.pairlist(args), body[[1]]))
function (y, x = 4)
x + y

There's no other way I've found (besides the above trick with alist) to generate one of these:

> as.symbol("")
Error in as.symbol("") : attempt to use zero-length variable name

But it's the value you have to provide in a formals list to get a mandatory argument.

Will Brannon
  • 91
  • 1
  • 2
  • 1
    Instead of your `alist()`-hack, you can simply call `bquote()` with no arguments to get what you want. – nbenn Dec 03 '20 at 10:02
-1

A little late for you, but may help others with the same issue. The "invalid formal argument list for "function"" error is a bug in RStudio. It happens when for some reason a breakpoint is set at the end bracket (}) of a function.

This happened to me when I was editing the R script while running it, and then the R studio crashed. the breakpoints were kept on the original lines, but the lines had moved...

All you need to do is remove the breakpoint, and you're good.

Yael
  • 9
  • 1
  • I'm sorry, but even though such a bug might exist/have existed in RStudio, your answer does in no way help with the question asked. The behavior described in the question can be observed in a non-RStudio environment as well. – nbenn Dec 03 '20 at 09:57