Defer expression evaluation without using `quote`

北战南征 提交于 2019-11-30 21:45:17

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