5

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?

eddi
  • 49,088
  • 6
  • 104
  • 155

1 Answers1

7

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 ) ;
}
eddi
  • 49,088
  • 6
  • 104
  • 155
Romain Francois
  • 17,432
  • 3
  • 51
  • 77
  • Thanks, this mostly answers the first part of the question - any idea why the second one (`fn3`) isn't working? – eddi Nov 01 '13 at 19:29
  • This is due to how we implemented `Function::operator()`. See https://github.com/RcppTeam/Rcpp/blob/master/inst/include/Rcpp/Function.h#L78 – Romain Francois Nov 01 '13 at 22:02
  • sorry, can you elaborate? – eddi Nov 01 '13 at 22:07
  • Actually, this was about how .Call evaluates its arguments. I've added meat to my answer. – Romain Francois Nov 02 '13 at 04:20
  • There's probably a way to not do the call to `dots` and get the unevaluated arguments using contexts as in http://stackoverflow.com/questions/19686892/passing-unevaluated-expressions-to-c-c/19711657#19711657 – Romain Francois Nov 02 '13 at 04:27
  • 1
    that's very interesting, thanks! based on your above explanation, the following - `fn3(substitute(substitute(a*2)), list(a = 1))` is another workaround – eddi Nov 04 '13 at 20:10