17

I'm a bit surprised by R's behaviour in a very specific case. Let's say I define a function square that returns the square of its argument, like this:

square <- function(x) { return(x^2) }

I want to call this function within another function, and I also want to display its name when I do that. I can do that using deparse(substitute()). However, consider the following examples:

ds1 <- function(x) {
  print(deparse(substitute(x)))
}

ds1(square)
# [1] "square"

This is the expected output, so all is fine. However, if I pass the function wrapped in a list and process it using a for loop, the following happens:

ds2 <- function(x) {
  for (y in x) {
    print(deparse(substitute(y)))
  }
}

ds2(c(square))
# [1] "function (x) "   "{"               "    return(x^2)" "}"  

Can anybody explain to me why this occurs and how I could prevent it from happening?

A. Stam
  • 2,148
  • 14
  • 29

1 Answers1

19

As soon as you use x inside your function, it is evaluated, so it "stops being an (unevaluated) expression" and "starts being its resulting values (evaluated expression)". To prevent this, you must capture x by substitute before you use it for the first time.

The result of substitute is an object which you can query as if it was a list. So you can use

x <- substitute(x)

and then x[[1]] (the function name) and x[[2]] and following (the arguments of the function)

So this works:

ds2 <- function(x) {
    x <- substitute(x)
    # you can do `x[[1]]` but you can't use the expression object x in a
    # for loop. So you have to turn it into a list first
    for (y in as.list(x)[-1]) {
        print(deparse(y))
    }
}
ds2(c(square,sum))
## [1] "square"
## [1] "sum"
akraf
  • 2,965
  • 20
  • 44
  • 2
    Amazing, thanks. Apparently, the technical term for this is a 'promise object'. While `x` is a promise, its elements (when assigned to `y`) are not promises anymore and therefore `substitute` returns a different value. – A. Stam Dec 20 '17 at 12:16
  • Oddly for my purposes (capture the variable name and put it as a title after some calculations in tidyverse), deparse was unnecessary. What's the difference between substitute(deparse(var)) and just substitute(var)? Also bonus q: is this still best practice with the new NSE from tidyverse? – JoeTheShmoe Oct 08 '20 at 16:22
  • @JoeTheShmoe `substitute()` returns a call object and `deparse()` turns a call object into a string. It is possible that if you provide the result of `substitute()` to your title-generating function, it will just call `deparse()` itself, but I don't really know. And in the tidyverse you rarely need `substitute()`, see the [Programming with dplyr](https://dplyr.tidyverse.org/articles/programming.html) vignette for all the ways you can react column titles etc. supplied by the user – akraf Oct 09 '20 at 07:53
  • Hmm interesting. And yeah, I read through that vignette which is what brought me here. I couldn't find a direct place that they talk about handling the input as a character. The old vignette I think had it with some weird mix of enquo() and sym() maybe, but I don't see it anymore, but maybe I've just missed it in there somwhere – JoeTheShmoe Oct 09 '20 at 15:50