I'd like to replicate the following R function in C/C++:
fn1 = function(a, b) eval(a, b)
fn1(substitute(a*2), list(a = 1))
#[1] 2
My first couple of attempts result in an error (and sometimes in a crash), probably because I'm not getting the environment from a list object (I looked at the R source code, and it was using a bunch of internal functions at this point which I don't think I can use), and I think that's what Rf_eval
wants and not the object itself.
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
Another attempt was trying to call base R eval
instead, which also gave the same error:
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
What's missing in each approach and how can I make both of them work?
In short, writing an implementation for eval in C is possible, but a huge amount of work that requires a lot of expertise and knowledge in several fields of computer science.
The Eval function evaluates the string expression and returns its value. For example, Eval("1 + 1") returns 2. If you pass to the Eval function a string that contains the name of a function, the Eval function returns the return value of the function. For example, Eval("Chr$(65)") returns "A".
Understanding Python's eval() You can use the built-in Python eval() to dynamically evaluate expressions from a string-based or compiled-code-based input. If you pass in a string to eval() , then the function parses it, compiles it to bytecode, and evaluates it as a Python expression.
C++ is a purely compiled language it has no means evaluate statements at runtime. if you want it you'll need to find a library that adds similar functionality or write your own. A good idea might be hosting a scripting language interpreter like lua.
Internally Rf_eval
expects an environment as its second argument. A list is not an environment. You can use list2env
on the R side to convert your list to an environment. So having this content in a separate .cpp
file :
#include <Rcpp.h>
using namespace Rcpp ;
// [[Rcpp::export]]
SEXP fn_impl( Language call, List env){
return Rf_eval( call, env ) ;
}
sourceCpp
the file and create a wrapper R function to facilitate creation of the environment:
sourceCpp( "fn.cpp")
fn <- function(call, list){
fn_impl( call, list2env(list) )
}
fn(substitute(a*2), list(a = 1))
If you don't want to create the environment, this is a bit more work, but you can navigate the call in C++ and substitute yourself. We do a lot of that in dplyr
for implementing hybrid evaluation.
For fn3
, I think this is about .Call
evaluating its arguments. See what happens if you replaced eval
by this function:
beval <- function(...){ print(match.call()); eval(...) }
So that you see how the function is called:
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))
You need non standard evaluation. One way is to send down a list of unevaluated arguments.
dots <- function(...) {
eval(substitute(alist(...)))
}
fn <- function(...){
args <- dots(...)
fn_impl(args)
}
Which you use at the C++ layer to construct the call to eval
and evaluate it:
#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 ) ;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With