0

I need to check the arity of a function in an Rcpp block at run time. What I would like to do is something akin to the following:

double loglikelihood(Rcpp::List data, Rcpp::List params, SEXP i, Rcpp::RObject custom_function) {
    Rcpp::Function f = Rcpp::as<Rcpp::Function>(custom_function);
    double res = 0.0;
    if (arity(f) == 3) {
        res = Rcpp::as<double>(f(data, param, i));
    } else if (arity(f) == 2) {
        res = Rcpp::as<double>(f(data, param));
    }
    return res;
}

However, the limited documentation I've seen for Rcpp does not seem to contain a function for checking the arity of an Rcpp::Function. Is there any way to do this?

kreld
  • 732
  • 1
  • 5
  • 16
  • 1
    If `custom_function` is an R function, you can check arity on the R side: `length(formals(custom_function))`. You could try to reproduce the `formals` function in C, but I wouldn't bother. – thc Jan 22 '21 at 20:26
  • `length(formals(custom_function))` is exactly what I need, but I need it on the C++ side, rather than the R side. I've solved this for now by passing a two element list with the function and the arity, rather than passing just the function, but that's a bit hacky. – kreld Jan 25 '21 at 08:54
  • 1
    since you are already calling R code from your C++ function, why not call the formals function using `Rcpp::Function` there too? That's what I meant when I said I wouldn't bother writing the function in C++. – thc Jan 25 '21 at 18:21
  • Oh I misunderstood. That's actually a really neat idea! – kreld Jan 26 '21 at 08:17

2 Answers2

1

The "limited documentation" (currently ten pdf vignettes alone) tells you, among other things, that all we have from R itself is .Call() returning SEXP and taking (an arbitrary number of) SEXP objects which can be a function. So all this ... goes back to the R API which may, or may not, have such an accessor which may or may not be public and supposed to be used by anybody but R itself.

These days we register compiled functions with R (typically in a file src/init.c or alike) where this number of argument is passed on as an second argument (beyond the function call name) when making the registration. Which suggests to me that it is not discoverable.

Dirk Eddelbuettel
  • 360,940
  • 56
  • 644
  • 725
0

So I solved this using a workaround that is a little bit clunky but after giving it some serious thought, this is the least clunky of three methods I tried implementing.

The method I ended up going with is checking the arity of the function on the R side using methods::formalArgs, wrapping the (function, arity) pair in a list and passing that to the Rcpp function like so:

double loglikelihood(Rcpp::List data, Rcpp::List params, 
                     SEXP i, Rcpp::RObject custom_function) {
    Rcpp::List l = Rcpp::as<Rcpp::List>(custom_function);
    Rcpp::Function f = Rcpp::as<Rcpp::Function>(l[0]);
    int arity = l[1];

    double res = 0.0;
    if (arity == 3) {
        res = Rcpp::as<double>(f(data, param, i));
    } else if (arity == 2) {
        res = Rcpp::as<double>(f(data, param));
    }
    return res;
}

As I mentioned, this is a bit clunky and it changes the signature of the funciton, which is not ideal. Another way of doing this would be to use the forgiveness-rather-than-permission approach and doing the control flow in a try-catch block, like so:

double loglikelihood(Rcpp::List data, Rcpp::List params, 
                     SEXP i, Rcpp::RObject custom_function) {

    Rcpp::Function f = Rcpp::as<Rcpp::Function>(custom_function);

    double res = 0.0;
    try {
        res = Rcpp::as<double>(f(data, param, i));
    } catch (const std::exception &e) {
        res = Rcpp::as<double>(f(data, param));
    }
    return res;
}

This approach is less clunky, but the problem with it is that it also catches other exceptions that might arise within f and silences them so they aren't passed to the user. It is possible that there are more fine-grained exceptions defined in Rcpp that would be able to catch the specific error of passing too many parameters, but if so I haven't found it.

Lastly, we could pass methods::formalArgs to loglikelihood and query the arity just before we need to use it, but I think this approach is the clunkiest of the three, because it requires us to pass formalArgs around a lot.

kreld
  • 732
  • 1
  • 5
  • 16