Defer expression evaluation without using `quote`

妖精的绣舞 提交于 2019-11-30 17:14:24

问题


I have created the following function/example as a generic way to display variable labels in tables and so forth:

#' 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 )
}

I'd rather the user not have to quote the expression being passed in, however.

replicate does just that, using this code:

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

Yet I don't understand how it works, and as is typical for attempts to replicate without comprehension, my attempts to simply copy this code or tinker with it have all failed.

How do I write a function with an unevaluated expression as input, without requiring the user to quote their expression? I assume the answer will rely heavily on lazy evaluation.


回答1:


Answer with eval and substitute

I think to do this in this case you just need eval(substitute(expr)). expr is a promise, and we can either get the value of the promise by using expr directly, or the content of the promise, by using substitute. See http://cran.r-project.org/doc/manuals/R-lang.html#Promise-objects for details. The content of the promise is a call, so we just eval that to get the new result.

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 ...

In a suggested edit, @user2103369 suggests that replicate is different because it uses sapply to get multiple evaluations, so it needs a function rather than a call.

Different behavior when default argument

Interestingly, the promise acts differently depending on if the argument is the default argument or added by the user; see below. I think SoDA addresses this but I don't have it handy. This function prints the value of the promise, evaluates it with eval, and then evaluates it directly.

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

Evaluating it directly results in an error when the user supplies the value.

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

But the default value works.

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

In a suggested edit, @user2103369 says that the default argument is evaluated inside the function, while an explicit argument is evaluated in the calling frame. So in this case, the user supplied value fails because a is not visible in the calling frame.

An alternate method using a function

However, to me (though the OP disagrees; I'm leaving this part for future readers of this answer), this feels like a case where it's more natural to use a function as the second parameter, like this; for one, this means the user doesn't have to know that it's called dat within the function.

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)
}

Then it can be called with an anonymous function, which is exactly what you're looking for, I think, except that the user has to type function(x) as well.

> 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 ...

Or in simple cases, as in your example, with just the name of the function.

> 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 ...


来源:https://stackoverflow.com/questions/15039599/defer-expression-evaluation-without-using-quote

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!