In R, how do I define a function which is equivalent to `deparse(substitute(x))`?

211 Views Asked by At

I want to write a function in R which grabs the name of a variable from the context of its caller's caller. I think the problem I have is best understood by asking how to compose deparse and substitute. You can see that a naive composition does not work:

# a compose operator
>  `%c%` = function(x,y)function(...)x(y(...))

# a naive attempt to combine deparse and substitute
> desub = deparse %c% substitute
> f=function(foo) { message(desub(foo)) }
> f(log)
foo

# this is how it is supposed to work
> g=function(foo) { message(deparse(substitute(foo))) }
> g(log)
log

I also tried a couple of variations involving eval.parent but with no luck. Any help is appreciated.


Clarification: I'm not looking for a synonym for deparse(substitute(...)), e.g. match.call()[[2]] - what I'm looking for is a way to define a function

desub = function(foo) {
    ...
    # What goes here?
}

such that the definition of f above produces the same answer as g. It should look like this:

> f=function(foo) { message(desub(foo)) }
> f(log)
log

Perhaps match.call could be of use in the body of desub above, but I'd like to know how. Thanks!

2

There are 2 best solutions below

0
On BEST ANSWER

Thanks to @egnha and @akrun for the brave attempts. After playing around a bit I found a solution that works.

This fragment:

desub <- function(y) {
  e1=substitute(y)
  e2=do.call(substitute,list(e1), env=parent.frame())
  deparse(e2)
}

gives:

> f <- function(x) message(desub(x))
> f(log)
log

Update:

With help from Mark Bravington on the R-devel list, I was able to generalize this to multiple frames. I thought I should post it here, because it's a bit more useful than the above, and because there was a tricky workaround involving (possibly buggy?) behavior in parent.frame().

# desub(v,0)=="v"
# desub(v,1)==deparse(substitute(v))
# desub(v,2)==name of v in grandparent's frame
# etc.
desub = function(y,n=1) {
  env=environment();
  for(i in 0:n) {
    y = do.call(substitute, list(substitute(y)), env=env)
    env = do.call(my_mvb_parent, list(), env=env)
  }
  deparse(y)
}

# helper:
#
# - using mvb.parent.frame fixes problems with capture.output and
#   weird cycling behavior in the built-in parent.frame
#
# - this wrapper makes mvb.parent.frame not throw an error when we get
#   to globalenv()
my_mvb_parent=function() {
  library(mvbutils)
  tryCatch(
    mvb.parent.frame(2),
    error=function(e) { globalenv()})
}

if(1) {
  # example code
  g2=function(t) {
    for(i in 0:5) {
      res=desub(t,i);
      print(res);
      res1=capture.output(desub(t,i))
      stopifnot(capture.output(res)==res1)
    }
  }
  g1=function(z) g2(z)
  g=function(y) g1(y)
  g(log)
  # prints:
  ## [1] "t"
  ## [1] "z"
  ## [1] "y"
  ## [1] "log"
  ## [1] "log"
  ## [1] "log"
}
4
On

As you surmised, this is an issue with environments. The reason why the function f does not give log when you call f(log), is that the environment in which substitute is called, namely the evaluation environment of desub, does not contain a binding to log.

The remedy is to evaluate the call to substitute in the proper environment, and modify desub accordingly:

desub <- function(x, env = parent.frame()) {
  deparse(eval(substitute(substitute(x)), envir = env))
}

Now f does what it was intended to do:

f(log)
#> log