Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

This entry is part 17 of 17 in the series Using R

Sometimes it is useful to write a wrapper function for an existing function. In this short example we demonstrate how to grab the list of arguments passed to a function and use it to call another function, taking care of optional arguments with or without default values.

Authors of R functions often specify default values for function arguments.  Invocation of the function may override defaults for some arguments and accept defaults for others. Inside of a function with default argument values, arguments always have a value even if it is NA or NULL — they are never ‘missing’.

Sanity checks in such functions often test arguments before they are used as in the following example:

f1 <- function(a='A',b=NULL) {
print( ifelse( is.null(a), 'a not specified', paste('a =',a) ) )
print( ifelse( is.null(b), 'b not specified', paste('b =',b) ) )
}

A few quick tests show how this works:

> f1()
[1] "a = A"
[1] "b not specified"
> f1(b='B')
[1] "a = A"
[1] "b = B"
> f1(a=NULL,b='B')
[1] "a not specified"
[1] "b = B"

So far so good.

But what happens when the function author doesn’t provide a default value for optional arguments and instead uses R’s missing() function to to determine whether the optional argument was specified?

f2 <- function(a='A',b) {
print( ifelse( missing(a), 'a not specified', paste('a =',a) ) )
print( ifelse( missing(b), 'b not specified', paste('b =',b) ) )
}

Here things look different:

> f2()
[1] "a not specified"
[1] "b not specified"
> f2(b='B')
[1] "a not specified"
[1] "b = B"
> f2(a=NULL,b='B')
[1] "a = "
[1] "b = B"

The output is equally correct though perhaps not as useful.

In general, creating functions where every optional argument has a default value of NULL is the recommended practice. However, sometimes we want to write a wrapper function for another function that does not adhere to this practice. In this case we need to convert our set of incoming arguments with some potential NULLs into a new set where some arguments are missing.

To accomplish this we will use the match.call() function to obtain a list of arguments when the function was called (throwing away the first item from match.call() which is the function name). We can then modify this list of actual arguments used to include what the wrapper function considers important default values.

The following example shows how to handle this:

callf2 <- function(a='A',b=NULL) {
print( ifelse( is.null(a), '# a not specified', paste('# a =',a) ) )
print( ifelse( is.null(b), '# b not specified', paste('# b =',b) ) )
argList <-  as.list(match.call(expand.dots = TRUE)[-1])
# Enforce inclusion of non-optional arguments
argList\$a <- a
do.call(f2,argList)
}

As the authors of the wrapper function, we specify non-NULL defaults for certain arguments. We will enforce that these arguments get included in the argList whether or not they were specified on the command line. Any arguments whose value is NULL will not appear in argList and will therefore be ‘missing’ when we

do.call(f2,argList)
:

> callf2()
[1] "# a = A"
[1] "# b not specified"
[1] "a = A"
[1] "b not specified"
> callf2(b='B')
[1] "# a = A"
[1] "# b = B"
[1] "a = A"
[1] "b = B"
> callf2(a=NULL,b='B')
[1] "# a not specified"
[1] "# b = B"
[1] "a not specified"
[1] "b = B"

Note how we pass on the default value of

a='A'
but still allow a user to set it to NULL in case the missing() test in f2() is important.

In general, function authors should avoid the missing() construct and should always specify a default value of NULL for optional arguments. This allows for more systematic sanity checking and passing of arguments to other functions. But there is a huge body of code out there that tests for missing() arguments and we need to know how work with it.

Best of luck writing robust, debuggable functions!