在C/C++中的eval和substitute

5

我希望你能在C/C++中复制以下R函数:

fn1 = function(a, b) eval(a, b)

fn1(substitute(a*2), list(a = 1))
#[1] 2

我的前几次尝试导致出现错误(有时会崩溃),可能是因为我没有从列表对象中获取环境(我查看了R源代码,这时使用了一堆我认为我不能使用的内部函数),而且我认为这就是Rf_eval所需的,而不是对象本身。

require(Rcpp)
require(inline)

fn2 = cxxfunction(signature(x = "SEXP", y = "SEXP"),
                 'return Rf_eval(x, y);')

fn2(substitute(a*2), list(a = 1))
# error, object 'a' not found

另一种尝试是调用基础R eval,但是也出现了相同的错误:

require(Rcpp)
require(inline)

fn3 = cxxfunction(signature(x = "SEXP", y = "SEXP"),
                 'Function base_eval("eval"); return base_eval(x, y);',
                 plugin = 'Rcpp')

fn3(substitute(a*2), list(a = 1))
# again, object 'a' not found

每种方法都存在什么不足,我应该如何使它们都能够正常工作?

1个回答

7

Rf_eval在内部期望一个环境作为第二个参数。列表不是一个环境。你可以在R语言中使用list2env将你的列表转换为环境。因此,将这段内容放在一个单独的.cpp文件中:

#include <Rcpp.h>
using namespace Rcpp ;

// [[Rcpp::export]]
SEXP fn_impl( Language call, List env){
    return Rf_eval( call, env ) ;
}

sourceCpp 解析文件并创建 R 函数包装器,以便简化环境创建:

sourceCpp( "fn.cpp")
fn <- function(call, list){
    fn_impl( call, list2env(list) )
}

fn(substitute(a*2), list(a = 1))

如果您不想创建环境,这需要更多的工作,但是您可以在C++中导航调用并自行替换。我们在实现混合评估时在中经常使用此方法。
对于,我认为这与<.Call>评估其参数有关。如果您将替换为此函数,请查看会发生什么:
beval <- function(...){ print(match.call()); eval(...) }

这样你就能看到函数是如何调用的:

fn3 = cxxfunction(signature(x = "SEXP", y = "SEXP"),
             'Function base_eval("beval"); return base_eval(x, y);',
             plugin = 'Rcpp')

fn3(substitute(a*2), list(a = 1))
# (function (...)
# {
#     print(match.call())
#     eval(...)
# })(a * 2, list(a = 1))

您需要进行非标准求值。一种方法是发送一个未求值参数的列表。
dots <- function(...) {
    eval(substitute(alist(...)))
}

fn <- function(...){
    args <- dots(...)
    fn_impl(args)
}

您在C++层使用哪些内容构建对eval的调用并进行评估:

#include <Rcpp.h>
using namespace Rcpp ;

// [[Rcpp::export]]
SEXP fn_impl( List args){
    Language call = args[0] ;
    List data = args[1] ;

    // now construct the call to eval: 
    Language eval_call( "eval", call, data ) ;

    // and evaluate it
    return Rf_eval( eval_call, R_GlobalEnv ) ;
}

谢谢,这基本上回答了问题的第一部分 - 有没有想法为什么第二个(fn3)不起作用? - eddi
这是由于我们如何实现Function::operator()所致。请参见https://github.com/RcppTeam/Rcpp/blob/master/inst/include/Rcpp/Function.h#L78 - Romain Francois
抱歉,你能详细说明一下吗? - eddi
实际上,这是关于 .Call 如何评估其参数的问题。我已经在我的答案中添加了内容。 - Romain Francois
可能有一种方法可以不调用dots函数,并使用上下文获取未评估的参数,就像在https://dev59.com/F2Ik5IYBdhLWcg3wAJ2E#19711657中所示。 - Romain Francois
1
非常有趣,谢谢!根据您上面的解释,以下代码 - fn3(substitute(substitute(a*2)), list(a = 1)) 是另一种解决方法。 - eddi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接