Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

eval and substitute in C/C++

Tags:

c

r

rcpp

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?

like image 378
eddi Avatar asked Nov 01 '13 15:11

eddi


People also ask

Is there eval in C?

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.

How do you use eval?

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

What does eval parse do?

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.

Does C++ have eval?

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.


1 Answers

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 ) ;
}
like image 145
Romain Francois Avatar answered Sep 24 '22 15:09

Romain Francois