3 min read

May your names fall into the Pit of Success

Have you ever made the following slip-up? You write a function in R that returns multiple values,

foo <- function(x) {
  list(once = x, twcie = 2 * x)
}

but when you call it, you get an unexpected result:

out <- foo(1)
paste("Once", out$once, "and twice", out$twice)
#> [1] "Once 1 and twice "

The culprit: a typo. foo() returns a component named twcie, which you actually meant to name twice.

R helps those who help themselves

Because list() can’t divine your intentions, you slipped. But there’s a simple trick to push you into the Pit of Success when it comes to naming multiple return values:

Delegate returning multiple values to an ad hoc function, rather than packing those values in a list.

That is, instead of writing

foo <- function(x) {
  list(once = x, twice = 2 * x)
}

create a wrapper to specify the shape of the return value, say,

result <- function(..., once, twice) {
  list(once = once, twice = twice)
}

then express foo() in terms of it:

foo <- function(x) {
  result(once = x, twice = 2 * x)
}

The crux of result() is that a call to it will only work when every argument after ... is specified by name.

With this simple rewrite, you can banish such typos with the help of safety guards built right into your IDE and the R language itself:

  • Auto-completion — Type the first few characters of result and press the TAB key. Your IDE will offer up result(), and subsequent tabbing will either show/insert once = and twice =.

  • Call-by-name enforcement — If you do make a typo in expressing the return value of foo(), say,

    foo <- function(x) {
      result(once = x, twcie = 2 * x)
    }

    then when you call foo(), you’ll now get an error message pinpointing the typo:

    foo(1)
    #> Error in result(once = x, twcie = 2 * x): argument "twice" is missing, with no default

Put R at your service

The function result() guides you in creating the correct return value. But writing the function result() is itself prone to errors.

For consider its definition:

result <- function(..., once, twice) {
  list(once = once, twice = twice)
}

Each argument name is repeated three times, each repetition a potential breakage. A misspelling of any one of those would still create a valid function. But when result() is called, it won’t necessarily throw an error, though its outcome will likely spoil your code.

Instead, you want to create result() directly from the list of named components that specify the shape. For instance, something like

result <- return_list("once", "twice")

return_list() should take a sequence of strings (possibly in conjunction with a vector of strings) and return a function that itself returns a list named by those strings.

Let’s do this with a bit of metaprogramming:

return_list <- function(..., nms = NULL) {
  nms <- vapply(c(..., nms), as.character, character(1))
  args <- lapply(setNames(nm = nms), as.name)
  
  body <- as.call(c(quote(list), args))
  fmls <- c(alist(... = ), args)
  fmls[] <- list(quote(expr = ))
  
  eval(call("function", as.pairlist(fmls), body), baseenv())
}

Explicit coercion to character validates the input, while the rest of the function automates the expression of the one-line function that wraps list().

Fall into the Pit

Expressing result() with return_list() now declares your intent:

result <- return_list("once", "twice")

foo <- function(x) {
  result(once = x, twice = 2 * x)
}

Type this out in your IDE—don’t copy-and-paste—to see for yourself that this is a robuster way to write foo(). When you get to typing result, you’ll find that you can TAB your way to completion, without error.