The goal of rpp is to provide a framework for preprocessing R code. Ultimately, this package aims at supporting static type checking for R, among other applications. At this time, dynamic type checking and zero-cost assertions are supported with the help of two other packages, {typed} and {chk}.
R is a weakly-typed interpreted language: variables can hold objects of any time, there is no explicit compilation stage, and no checks are carried out during run time. This is great for interactive ad-hoc analysis, but can make complex projects more difficult to maintain and debug. In contrast, in strongly-typed languages, the type of each variable is declared beforehand. Adding a static type checking layer to R would make it easier to improve stability in complex projects. With preprocessing, this can be done with no cost at runtime.
This package operates on the notion of different source code “modes”:
- Development (or dev): the code the developer of the package works on
- Production (or prod): the code that is run by typical users
Expensive checks can be enabled in development mode, while production code is kept lean and fast. In production mode, all checks are completely removed (elided) from the source code. Only production code ends up in version control, this ensures compatibility with existing tooling. Code can be quickly and losslessly converted between development and production modes with rpp::rpp_to_dev()
and rpp::rpp_to_prod()
.
The rpp package does not implement any code transformations. Rather, it provides the infrastructure for plugins which are responsible for converting development code to production code and back. Currently, two plugins exist (in forks of existing packages in this GitHub organization):
- {typed} provides dynamic type checking via the
typed::rpp_elide_types()
plugin - {chk} provides zero-cost assertions via the
chk::rpp_elide_chk_calls()
plugin
Plugins are configured in the DESCRIPTION
file.
Install the development version of rpp and the associated packages from GitHub with:
# install.packages("devtools") devtools::install_github("Q-language/rpp") devtools::install_github("Q-language/typed") devtools::install_github("Q-language/chk")
Once on CRAN, you can also install the released version of rpp with:
install.packages("rpp")
library(typed) #> #> Attaching package: 'typed' #> The following object is masked from 'devtools_shims': #> #> ? #> The following object is masked from 'package:utils': #> #> ?
The following function makes use of dynamic type assertions provided by the {typed} package:
foo <- Character()? function(x = ?Character()) { Character()? out <- paste("foo:", x) out }
This is still valid R code, because {typed} overloads the ?
operator. The function can only be called with a character vector, other types give an error:
foo("bar") #> [1] "foo: bar" foo(1) #> Error: In `foo(1)` at `check_arg(x, Character())`: #> wrong argument to function, type mismatch #> `typeof(value)`: "double" #> `expected`: "character" foo(mean) #> Error: In `foo(mean)` at `check_arg(x, Character())`: #> wrong argument to function, type mismatch #> `typeof(value)`: "closure" #> `expected`: "character"
These checks are useful, but slow down the code. If this function lives in a package that is configured with the typed::rpp_elide_types()
plugin, running rpp::rpp_to_prod()
results in the following code:
foo <- function(x ) { # !q foo <- Character()? function(x = ?Character()) { out <- paste("foo:", x) # !q Character()? out <- paste("foo:", x) out }
Running rpp::rpp_to_dev()
brings back the original code with the checks. The production version is not particularly pretty, but does the job.
The fork of the {chk} package in this organization is configured for use with rpp. Clone the repository, start an R session, and run rpp::rpp_to_dev()
and rpp::rpp_to_prod()
to see rpp in action.
This example show how to configure your project with rpp and dynamic type checking via {typed}. Note that this requires {typed} to be installed from this organization’s fork. Adapt as necessary for use with other plugins.
rpp currently works only on projects with a DESCRIPTION
file, and operates only on files in the R/
directory. Add the following line to DESCRIPTION
:
Config/rpp/plugins: list(typed::rpp_elide_types())
With this configuration, rpp::rpp_to_dev()
and rpp::rpp_to_prod()
will run the plugin implemented by {typed}.
In the case of {typed}, two more tweaks are necessary:
- The package must be listed in the
Imports
section of theDESCRIPTION
file. - It should be imported to enable the overload of the
?
operator and to access the type declarations.
For the latter, add import(typed)
to NAMESPACE
, or #' @import typed
to an .R
source file if you are using roxygen2.
A project can be automated to change to production mode when devtools::document()
is run. Add the rpp::rpp_prod_roclet()
to the list of roclets:
Roxygen: list(markdown = TRUE, roclets = c("collate", "namespace", "rd", "rpp::rpp_prod_roclet"))
RStudio users can also edit the .Rproj
file to convert to prod when Ctrl+Shift+D is pressed:
PackageRoxygenize: rd,collate,namespace,rpp::rpp_prod_roclet
With this configuration, running devtools::document()
includes the effect of calling rpp::rpp_to_prod()
.
The creation of a new plugin and the integration with roxygen2 is described in vignette("plugins", package = "rpp")
.
Please note that the rpp project is released with a Contributor Code of Conduct. By contributing to this project, you agree to abide by its terms.