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?
As soon as you use x
inside your function for the first time, it is evaluated, so it "stops being an unevaluated call" (the technical term in R for this is "promise") and "starts being its resulting values". To prevent this, you must capture x
by substitute
before you use it for the first time.
(The reason for this behaviour lies in R's nature of being a language that does so-called "lazy evaluation", more on this and promises in "Advanced R"´)
The result of substitute
is an object of a type named "call" which you can query as if it was a list. So inside a function 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"
Note that to complicate matters, substitute()
only behaves like this when it is called inside a function and not outside on the top level (global environment). Outside of a function it turns its argument into a call object without replacing anything. So outside of a function, substitute()
acts like quote()
. That can be confusing if you try to tinker around with it:
f <- function(x){
x <- substitute(x)
deparse(x)
}
# Replaces "x"
f("hi")
## "\"hi\""
# Does not replace x
x <- "hi"
deparse(substitute(x))
## "x"