14

I have a function which does extra things if a non-empty value is supplied to what otherwise would be an optional parameter. If the argument is not supplied, the function doesn't do anything extra.

Is it better to use NA or NULL? What are the advantages of either?

For example, if I use NA, then I can quickly check which arguments were not supplied by using: is.na(as.list(environment())) in the function body, which does not work with is.null.

Here is an example of why I want to use NA

I am trying to build an R connector to the Geckoboard bar chart API. It has many optional arguments. If I do the following, it is very easy to use the package jsonlite to supply the optional arguments.

get_barchart_json <- function(data, x_axis = list(labels = NA, type = NA), y_axis = list(format = NULL, unit = NULL)){
    payload <- "{"
    textappend(payload) <- '"series": [{"data":['
    textappend(payload) <- paste0(data, collapse = ",")
    textappend(payload) <- ']}]'

    if(any(!is.na(x_axis))){

        textappend(payload) <- ","
        textappend(payload) <- jsonlite::toJSON(x_axis, auto_unbox = TRUE)

    }

    if(any(!is.na(y_axis))){

        textappend(payload) <- ","
        textappend(payload) <- jsonlite::toJSON(y_axis, auto_unbox = TRUE)

    }



    # finish construction
    textappend(payload) <- '}'

    return(payload)

}

which returns, for example:

cat(get_barchart_json(data = c(1,2,3,4), x_axis = list(labels = c("a", "b", "c", "d"), format = "text"), y_axis = list(format = 'decimal')))

NB: textappend is:

`textappend<-` <- function(payload, value){
 payload <- paste0(payload, value)    
 payload
}
Alex
  • 15,186
  • 15
  • 73
  • 127

3 Answers3

8

NA is for data values which are missing from data sets. It will also propagate in places that NULL will not, as it can be type-coerced. For an /optional/ value, use NULL.

I feel it's kind of an anti-pattern to allow missing argument values - it's hard to know which values you are leaving out. With NULL, the 3rd argument is always the 3rd argument, etc...

See NA vs. NULL.

BadZen
  • 4,083
  • 2
  • 25
  • 48
  • 1
    I don't understand what you mean by "allow missing argument values" – Alex Apr 14 '15 at 05:02
  • 1
    Actually this is not quite a duplicate of, but the thing I'm talking about is already written up here: http://stackoverflow.com/questions/28370249/correct-way-to-specifiy-optional-arguments-in-r-functions – BadZen Apr 14 '15 at 05:03
  • 1
    Thanks, very helpful link. So canonically it seems that one should use `NULL`, yet, using `NA` has its advantages as well. – Alex Apr 14 '15 at 05:06
  • Yup. Plausibly within the realm of "preference", though now you have my opinion on which is cleaner. =) – BadZen Apr 14 '15 at 05:08
  • 2
    "it's hard to know which values you are leaving out": that's why named arguments in function calls are a good thing! – Hong Ooi Apr 14 '15 at 05:41
5

You actually have 3 options: NA, NULL, or no default. Under most circumstances, NULL is the best option.

function(x, optional_arg = NULL)
{
  if(is.null(optional_arg))
  {
    # do something
  }
  # rest of body code
}

This doesn't work if you want users to be able do pass NULL as a legitimate value to optional_arg. In that case, use

function(x, optional_arg)
{
  if(missing(optional_arg))
  {
    # do something
  }
  # rest of body code
}

A default of NA is only useful for cases where the optional arg should take one of three values, and it makes more sense to store it as a logical value rather than a string.

function(x, optional_arg = NA)
{
  if(is.na(optional_arg))
  {
    # do something
  } else if(optional_arg)
  {
    # do something else
  } else  # case of optional_arg == FALSE
  {
    # do a third thing
  }
  # rest of body code
}
Richie Cotton
  • 118,240
  • 47
  • 247
  • 360
  • 2
    Missing starts being an annoyance when you get to function that are deeply nested and you want to control some elements at a higher level, setting it to NULL is then much easier than writing the code to do multiple calls to the lower level function based on the higher up parameters supplied. – Hansi Apr 14 '15 at 09:56
  • @Hansi. Yep. Like I said, `NULL` is usually the best option. – Richie Cotton Apr 14 '15 at 14:11
0

As Hansi mentioned in the comments, relying on missing is fine for interactive use, but is annoying for programmatic use with nested functions that pass arguments to downwards. Consider the following minimum example.

Suppose we have a function that does something special when arg2 is missing. We check using the standard missing approach.

> func1 = function(arg1, arg2) {
  if (missing(arg2)) print("no arg2")
  print(arg1)
  }
> func1("abc")
[1] "no arg2"
[1] "abc"

This works fine. However, what if we call this from another function that passes arguments on and has a default?

> func2 = function(arg1, arg2 = "def") {
  func1(arg1 = arg1, arg2 = arg2)
  }
> func2("abc")
[1] "abc"
> func2("abc", arg2 = )
[1] "abc"
> func2("abc", arg2 = NULL)
[1] "abc"
> func2("abc", arg2 = NA)
[1] "abc"

We can't seem to trigger the special feature when arg2 is missing because it's not easy to set the arg2 to be missing from another function that never had a missing argument. Maybe not possible.

Also discussed on this blogpost.

Community
  • 1
  • 1
CoderGuy123
  • 6,219
  • 5
  • 59
  • 89