Deep thoughts and gists
by Paweł Piątkowski

Statically-typed R

Recently, I’ve been thinking about providing more type safety for R APIs – below you can find an early attempt to address this. Of course, because of the very nature of R, type checking can be only run-time, and we have to sacrifice features like lazy evaluation (only for typed arguments; other arguments are still lazy), but – in my opinion – that’s how APIs should be written. All the magic can stay inside, hidden from others’ eyes :-)

I struggled a bit with the syntax. The result might not be ideal, but I think it’s clear and rather elegant – any function constructing an object of a certain class (e.g., character or integer, but also constructors for your own classes) is allowed as type. The binary operator %:% is used for defining the output type of a function. For typed arguments, argument name must be prefixed with the type (which is dropped during runtime), as can be seen below:

int_mult = integer %:% function(integer..x, integer..y = 1L) { x * y }
int_mult(2L, 3L) # [1] 6
int_mult(2, 3) # ERROR: is(arg, types[[arg_name]]) is not TRUE

You can mix typed and untyped arguments in a single definition:

my_fun = character %:% function(character..string, integer..times, opt) {
  if (is.list(opt)) {
    rep(string, times)
  } else {
    NA_character_
  }
}
my_fun(string = "a", times = 2L, opt = list()) # [1] "a" "a"
my_fun("b", 3L, opt = -1) # [1] NA