GSOC Week 5
Automated Generation of R wrapper for libcpp_test
This week was awesome as I successfully created a prototype of modified autowrap which generates R code that wraps some important sections of libcpp_test like overloaded constructors, object attributes, getter & setter functions for attributes as a single active binding in R6 class, conversion providers for basic types like void, integer, float, enum, character, cpp class object and also stl containers like pair and set. It also takes into consideration the autowrap directives when generating R code.Support for pass by reference argument is also there. Currently supported directives are:
wrap-ignore, wrap-pass-constructor, wrap-doc (injection of R specific comments), wrap-as & wrap-constant
Note that we cannot incorporate all the directives, as some are specifically for C++ to python wrapping whereas in our R code we are working with a wrapped python object. For example - #wrap-hash directive is not required, as the wrapped python object is already hashable and using reticulate we can directly compare such python objects for equality.
For now, converters for nested data structures(vector & map, wrappers for templated classes, static and free functions are also not provided.
R Code Generation
The modified version of autowrap aimed at automated production of R wrappers from pxd declarations is hosted here.
.\autowrap folder there are two files py_libcpp_test.R and libcpp_test.R. The former is automatically generated while the latter contains manually written R wrappers.
We can generate the R wrapper code corresponding to any pxd declaration file using the command:
autowrap –out output.R input.pxd
where we can specify the relative path of our pxd file and out.R will be the name of the file containing wrapper R code.
The produced R file imports all the necessary R packages and creates object to access required python modules using
Through the below example, we can get a good idea how constructor overloading is being handled in R.
Here, we have a main constructor initialize which calls two different functions - init_0 & init_1 depending on the argument length and size. It calls init_0 when the argument is integer, else it calls init_1 when the argument is an R6 object of Int class. An error is thrown whenever a wrong argument is provided. It also does type checking inside the two functions. Here, we can also see that we are able to get some documentation in the form of R comments with the help of #wrap-doc directive.
Attribute getter and setter in the form of active binding
We can achieve both functionality in the form of a single active binding. Below, we can see how it is being done for attribute i_ of Int class.
If we are simply accessing the attribute, example :
object$i_ then our active binding behaves as getter , else when we try to set this attribute like
object$i_ <- 1, it then behaves as a setter.
Function wrapping & Pass by reference arguments
The technique used is very similar to what autowrap actually does when wrapping c++ functions in python. Here’s an example to show a wrapped function where an argument is passed by reference.
Here, we first create a temporary python object using a copy of the argument. Then we, call the python function passing these objects as arguments. We save the result of our function call if it returns a non-void type. Since, here it doesn’t return anything so we just call the function. In the next step, we convert the python arguments back to R type. Finally, in the tryCatch block we perform changes in the original R arguments which were passed and silently return NULL (function doesn’t print NULL when called). The reason for returning NULL is that underlying C++ function has void return type and another reason is that by default a function in R will return the value of the last statement executed in the function. So, we don’t want our function to leak unnecessary values.
For next week, I would write tests for libcpp_test and refactor the current code as well.