5

I have a function like so:

callFunc <- function (f) {
    f(1)
}

f could be (for example) f <- function (x) x. To simplify things, let's say that I know that f should return a numeric and take in a single numeric.

I'd like to move callFunc to C, but still have the function f defined in R, i.e.

.Call('callFunc', function (x) x)

I'm struggling with how to evaluate my callback on the C side. I have it like this at the moment:

#include <R.h>
#include <Rdefines.h>

SEXP callFunc (SEXP i_func) {
    return i_func(1);
}

(Then to test it:

R CMD SHLIB test.c
# then in R
dyn.load('test.so'); .Call('callFunc', function (x) x)

)

Of course, the above does not work because

  • I have not coerced i_func into the appropriate closure form; I'm not sure how to do this (there are AS_foo macros in Rdefines.h, but no AS_CLOSURE).
  • I haven't even told the C code that i_func should take in a numeric and return a numeric, so how can it even evaluate?

Could anyone give me pointers on how to go about doing this? I'm working my way through writing R extensions but this is rather long and I haven't found what I'm after yet. Also there is this question on R-help but the answer looks like they implemented the callback f in C as well, rather than leaving it as an R object.

mathematical.coffee
  • 55,977
  • 11
  • 154
  • 194

2 Answers2

3

This is very easy with Rcpp:

Rcpp::cppFunction("SEXP callFun(Function f) {
  return f(1);
}")

callFun(function(x) x + 10)
# [1] 11
hadley
  • 102,019
  • 32
  • 183
  • 245
  • cheers! Do you know how it would be done without Rcpp, for completeness? (avoiding adding extra dependencies for example) – mathematical.coffee May 07 '14 at 00:18
  • No, and I have spent long enough reading the disaster of r internals documentation that I have no intention of finding out ;) – hadley May 07 '14 at 00:24
3

For completeness, here is how you'd do it without Rcpp (I took my cue from the XML package, which lets you provide handlers). You construct a call (first argument is the function as a SEXP, subsequent arguments are the function arguments, all SEXP) and use eval.

// takes a callback and evaluates it (with argument 1), returning the result.   
SEXP callFunc(SEXP func) {                                      
    SEXP call, ans;                                              

    PROTECT(call = allocVector(LANGSXP, 2)); // call + arg       

    SEXP c;                                                      
    c = call;                                                    

    SETCAR(call, func);                                          
    c = CDR(c);                                                  

    // for some reason if I just SETCDR(c, ScalarReal(1.0)) I get
    // a memory fault, but using SETCAR like this is fine. 
    SETCAR(c, ScalarReal(1.0));                      

    ans = eval(call, R_GlobalEnv);  // maybe PROTECT?
    UNPROTECT(1);             

    return(ans);              
}   

From R:

.Call('callFunc', function (x) sin(x))
mathematical.coffee
  • 55,977
  • 11
  • 154
  • 194
  • I'm not sure, but I think you are missing a couple of `PROTECTS/UNPROTECTS`. My understanding is that every declaration of `SEXP` should be accompanied by `PROTECT/UNPROTECT`. Thanks for adding this! – Joseph Wood Jun 27 '21 at 14:59