在R中,我如何定义一个等同于`deparse(substitute(x))`的函数?

时间:2016-12-11 05:29:58

标签: r reflection functional-programming lazy-evaluation

我想在R中编写一个函数,它从调用者的调用者的上下文中获取变量的名称。我认为通过询问如何撰写deparsesubstitute可以最好地理解我遇到的问题。你可以看到天真的构图不起作用:

# 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

我还尝试了一些涉及eval.parent但却没有运气的变体。任何帮助表示赞赏。

澄清:我不是在寻找deparse(substitute(...))的同义词,例如match.call()[[2]] - 我正在寻找的是一种定义函数的方法

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

以上f的定义与g产生相同的答案。它应该是这样的:

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

也许match.call可以在desub以上的身体中使用,但我想知道如何。谢谢!

2 个答案:

答案 0 :(得分:2)

正如您所推测的,这是环境问题。调用f时函数log未提供f(log)的原因是substitute被调用的环境,即desub的评估环境},不包含对log的绑定。

补救措施是在适当的环境中评估对substitute的调用,并相应地修改desub

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

现在f完成了它的目的:

f(log)
#> log

答案 1 :(得分:2)

感谢@egnha和@akrun的勇敢尝试。在玩了一下后,我发现了一个有效的解决方案。

这个片段:

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

给出:

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

更新

在R-devel列表的Mark Bravington的帮助下,我能够将其推广到多个帧。我想我应该在这里发布它,因为它比上面的更有用,并且因为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"
}