【问题标题】:Defer expression evaluation without using `quote`延迟表达式评估而不使用 `quote`
【发布时间】:2013-02-23 10:42:53
【问题描述】:

我创建了以下函数/示例作为在表格等中显示变量标签的通用方式:

#' Function to prettify the output of another function using a `var.labels` attribute
#' This is particularly useful in combination with read.dta et al.
#' @param dat A data.frame with attr `var.labels` giving descriptions of variables
#' @param expr An expression to evaluate with pretty var.labels
#' @return The result of the expression, with variable names replaced with their labels
#' @examples
#' testDF <- data.frame( a=seq(10),b=runif(10),c=rnorm(10) )
#' attr(testDF,"var.labels") <- c("Identifier","Important Data","Lies, Damn Lies, Statistics")
#' prettify( testDF, quote(str(dat)) )
prettify <- function( dat, expr ) {
  labels <- attr(dat,"var.labels")
  for(i in seq(ncol(dat))) colnames(dat)[i] <- labels[i]
  attr(dat,"var.labels") <- NULL
  eval( expr )
}

不过,我希望用户不必引用传入的表达式。

replicate 就是这样做的,使用以下代码:

eval.parent(substitute(function(...) expr))

但我不明白它是如何工作的,而且就像在没有理解的情况下尝试复制的典型情况一样,我试图简单地复制此代码或修改它的尝试都失败了。

如何在不要求用户quote 他们的表达式的情况下编写一个以未计算表达式作为输入的函数?我认为答案将在很大程度上依赖于惰性评估。

【问题讨论】:

  • 关于replicate 的作用,我没有测试任何东西,但我认为function(...) 是一种将任何附加参数传递给表达式的方法,eval.parent 在父级中对其进行评估环境,而不是功能环境。这可能是它在您的情况下不起作用的原因,因为 dat 不在父环境中。
  • 另外,在建议的编辑中,@user2103369 建议 replicate 不同,因为它使用 sapply 来获得多个评估,因此它需要一个函数而不是调用。

标签: r lazy-evaluation


【解决方案1】:

用 eval 和替换回答

我认为在这种情况下你只需要eval(substitute(expr))expr是一个promise,我们既可以直接使用expr获取promise的值,也可以使用substitute获取promise的内容。有关详细信息,请参阅http://cran.r-project.org/doc/manuals/R-lang.html#Promise-objects。承诺的内容是call,所以我们只需eval 即可得到新的结果。

prettify <- function( dat, expr ) {
  labels <- attr(dat,"var.labels")
  for(i in seq(ncol(dat))) colnames(dat)[i] <- labels[i]
  attr(dat,"var.labels") <- NULL
  eval(substitute(expr))
}

> prettify( testDF, str(dat))
'data.frame':   10 obs. of  3 variables:
 $ Identifier                 : int  1 2 3 4 5 6 7 8 9 10
 $ Important Data             : num  0.336 0.9479 0.1379 0.94 0.0484 ...
 $ Lies, Damn Lies, Statistics: num  1.398 0.654 0.268 -0.397 -0.41 ...

在建议的编辑中,@user2103369 建议 replicate 不同,因为它使用 sapply 来获得多个评估,因此它需要一个函数而不是调用。

默认参数时的不同行为

有趣的是,根据参数是默认参数还是由用户添加,promise 的行为会有所不同;见下文。我认为 SoDA 解决了这个问题,但我手边没有。这个函数打印出promise的值,用eval求值,然后直接求值。

foo <- function(a, b=a+1) {
  print(substitute(b))
  print(eval(substitute(b)))
  b
}

当用户提供值时,直接对其求值会导致错误。

> foo(a=2, b=a+1)
a + 1
[1] 3
Error in foo(a = 2, b = a + 1) : object 'a' not found

但默认值有效。

> foo(a=2)
a + 1
[1] 3
[1] 3

在建议的编辑中,@user2103369 表示默认参数在函数内部计算,而显式参数在调用框架中计算。所以在这种情况下,用户提供的值会失败,因为 a 在调用框架中不可见。

使用函数的替代方法

但是,对我来说(尽管 OP 不同意;我将这部分留给未来的读者阅读),这感觉像是使用函数作为第二个参数更自然的情况,就像这样;一方面,这意味着用户不必知道它在函数中被称为dat

prettify <- function( dat, FUN ) {
  f <- match.fun(FUN)
  labels <- attr(dat,"var.labels")
  for(i in seq(ncol(dat))) colnames(dat)[i] <- labels[i]
  attr(dat,"var.labels") <- NULL
  f(dat)
}

然后可以使用匿名函数调用它,我认为这正是您正在寻找的,除了用户还必须输入function(x)

> prettify( testDF, function(x) str(x) )
'data.frame':   10 obs. of  3 variables:
 $ Identifier                 : int  1 2 3 4 5 6 7 8 9 10
 $ Important Data             : num  0.296 0.707 0.883 0.821 0.724 ...
 $ Lies, Damn Lies, Statistics: num  -1.1506 0.4846 -1.824 -0.397 0.0898 ...

或者在简单的情况下,如您的示例,仅使用函数的名称。

> prettify( testDF, str)
'data.frame':   10 obs. of  3 variables:
 $ Identifier                 : int  1 2 3 4 5 6 7 8 9 10
 $ Important Data             : num  0.296 0.707 0.883 0.821 0.724 ...
 $ Lies, Damn Lies, Statistics: num  -1.1506 0.4846 -1.824 -0.397 0.0898 ...

【讨论】:

  • 我曾想过将函数作为参数(在这种情况下,肯定会想写一些 ... 动作),但表达式版本对我来说似乎更简单,所以我的问题仍然存在。
  • 很公平。我觉得它应该在没有eval 的情况下工作,因为expr 是一个承诺,并且在它被调用之前不会被评估,但在我的测试中,它没有。希望我面前有我的 SoDA 副本,这样我就可以重新阅读它是如何工作的。
  • 感谢@user2103369 提出一些修改建议。我已经合并了他们的内容。 (在我看来,评论是与回答者就帖子内容进行互动的更好方式;直接编辑对于小的拼写/语法问题或回答者不再活跃时更好。)
  • 强烈同意@Aaron 的观点,即使用function 作为第二个参数是解决此问题的 R 方法。
猜你喜欢
  • 1970-01-01
  • 2017-02-15
  • 2021-05-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多