# install.packages("devtools")
devtools::install_github("EthanSansom/typewriter")typewriter
A type system for R inspired by the {typed} package. Enforce object and function argument types with ease.
This is a copy of the README for my package {typewriter}, which is currently under construction. You can follow the development of {typewriter} here.
{typewriter} is an R package which implements a minimal system for adding type safety to objects and functions in R. The following functions form the core functionality of the {typewriter} package:
%<~%, a typed assignment operator for functions and other objectsconst(), for creating constantsdots(),optional(),required(), for setting the type of function argumentsreturns(), for setting the return type of functions
Installation
You can install the development version of {typewriter} from GitHub with:
Features
This package, for the time being, lives primarily in a long Notion document. Below are some (un-run) examples which showcase the planned {typewriter} interface.
The typed assignment operator %<~% takes a symbol as it’s left-hand-side argument and an arbitrary check function as it’s right-hand-side value. Check functions must satisfy the following criteria:
- The first argument must be an object to check
- On a successful check, the first argument is returned
- On a failed check, an error is emitted
The {ckh} package’s family of chk_*() functions and the {checkmate} package’s family of check_*() functions both use this pattern.
# Create a function to check that it's input `x` is an integer
check_integer <- function(x) {
if (is.integer(x)) {
return(x)
}
stop("Object must be an integer.")
}
# Type `my_int` as an integer
my_int %<~% check_integer(10L)
print(my_int)
#> [1] 10The %<~% operator retrieves the value to assign from the first argument supplied to the right-hand-side check. Anytime a value is re-assigned to the symbol my_int in the global environment, the check check_integer() will be re-run.
try(my_int <- "A")
#> Error: Object must be an integer.You can supply additional arguments to a right-hand-side check function, which are evaluated once during assignment and are then supplied as arguments in subsequent checks.
library(chk)
probability %<~% chk::chk_range(c(0, 0.5, 0.75), range = c(0, 1))
try(probability <- 12)
#> Error:
#> ! `probability` must be between 0 and 1, not 12.The helper function const() checks that it’s input is unchanged and can be used to declare constants.
dimensions %<~% const(c(10, 8))
try(dimensions <- c(2, 4))
#> Error:
#> ! Can't assign a new value to the constant `dimensions`.The %<~% operator can also be used to type the arguments and return value of a function. The syntax is the same as before, but now we assign a check to a function argument instead of a symbol.
sum_int %<~% function(
dots = dots(check_integer), # `dots()` is helper to assign a type to `...`
na.rm = chk::chk_flag(FALSE) # `na.rm` must be `TRUE` or `FALSE` (default)
) {
sum(..., na.rm = na.rm)
}
# The result is a typed function
print(sum_int)
#> <typed>
#> function(..., na.rm = FALSE) {
#> lapply(list(...), check_integer)
#> chk::chk_flag(na.rm)
#>
#> sum(..., na.rm = na.rm)
#> }You can specify the return type of a function using returns() as well as required or optional arguments using required() and optional().
# Make an alias for a <numeric> vector
num <- chk::chk_numeric
# Create a typed sum function which requires numeric arguments, of
# which only `x` and `y` are required, and has a numeric return type.
my_sum %<~% function(
x = required(num),
y = required(num),
z = optional(num),
returns = returns(num)
) {
if (rlang::is_missing(z)) { z <- 0 }
x + y + z
}
# The resulting typed function
print(my_sum)
#> <typed>
#> function(x, y, z) {
#> rlang::check_required(x)
#> num(x)
#> rlang::check_required(y)
#> num(y)
#> if (!rlang::is_missing(z)) num(z)
#>
#> if (rlang::is_missing(z)) { z <- 0 }
#> num(x + y + z)
#> }Inspiration
This package was primarily inspired by the {typed} package, which implements a type system using an overloaded ? operator for type assignment.