Here are some recommendations to use {typed} in your own package and define custom types..
Call this to set up your package so you can use ‘typed’, by editing the DESCRIPTION file and editing or creating ‘R/your.pkg-package.R’
Here’s an example where we define types for the return value, for all
arguments, and for a msg
variable in the body.
The use of roxygen2 tags is standard, except that you’ll need to make
sure to add the @name
tag.
#' add_or_subtract
#'
#' @param x double of length 1
#' @param y double of length 1
#' @param subtract whether to subtract instead of adding
#' @export
#' @name add_or_subtract
add_or_subtract <- Double(1) ? function (
x = ? Double(1),
y = ? Double(1),
subtract = FALSE ? Logical(1, anyNA = FALSE)
) {
Character(1) ? msg
if(subtract) {
msg <- "subtracting"
message(msg)
return(x - y)
}
msg <- "adding"
message(msg)
x + y
}
The created function will be the following:
add_or_subtract
#> # typed function
#> function (x, y, subtract = FALSE)
#> {
#> check_arg(x, Double(1))
#> check_arg(y, Double(1))
#> check_arg(subtract, Logical(1, anyNA = FALSE))
#> declare("msg", Character(1))
#> if (subtract) {
#> msg <- "subtracting"
#> message(msg)
#> return(check_output(x - y, Double(1)))
#> }
#> msg <- "adding"
#> message(msg)
#> check_output(x + y, Double(1))
#> }
#> # Return type: Double(1)
#> # Arg types:
#> # x: Double(1)
#> # y: Double(1)
#> # subtract: Logical(1, anyNA = FALSE)
We see that ?
is not present in the generated code,
instead we have check_arg()
, declare()
,
check_output()
. The substitution is done by the first
?
, it is both for efficiency and readability, unfamiliar
users might be intimidated by ?
and calls to ?
don’t print nicely in base R.
Note that you’re free to use these functions directly in your code if
you don’t like the ?
Syntax.
A way to define custom types is to wrap existing ones and add constraints
Fruit <- function() {
Character(
length = 1,
... = "`value` is not a fruit!" ~ . %in% c("apple", "pear", "cherry")
# we could have several args named ... to apply multiple checks
)
}
Fruit() ? x <- "potatoe"
#> Error: `value` is not a fruit!
#> `value %in% c("apple", "pear", "cherry")`: FALSE
#> `expected`: TRUE
The type Any()
is the most general, but in many case
it’s a good idea to start from a more restricted type so you get its own
checks for free.
Here’s a case where starting from Any()
makes sense
:
Ggplot <- function() {
Any(... = "Expected a ggplot object" ~ ggplot2::is.ggplot(value))
}
Ggplot() ? x <- 1
#> Error: Expected a ggplot object
#> `ggplot2::is.ggplot(value)`: FALSE
#> `expected`: TRUE
A custom type might also just be a restriction on an existing type using existing arguments.
In the following example we apply a simple restriction to an existing
type, using the existing argument length
.
Here we restrict the length but keep other args flexible by forwarding them:
ScalarInteger1 <- function(null_ok = FALSE, ...) {
Integer(length = 1, null_ok = null_ok, ...)
}
ScalarInteger1() ? x <- c(1L, 2L)
#> Error: length mismatch
#> `length(value)`: 2
#> `expected`: 1
Here we remove all flexibility:
We can define a check function and use
as_assertion_factory()
on it.
check_is_ggplot <- function(x) {
if(!ggplot2::is.ggplot(x)) {
msg <- "Class mismatch"
info1 <- "Expected a ggplot object"
info2 <- sprintf("Got an object of class <%s>", paste(class(x), collapse = "/"))
rlang::abort(
c(msg, i = info1, x = info2)
)
}
}
Ggplot <- as_assertion_factory(check_is_ggplot)
Ggplot() ? x <- 1
#> Error: Class mismatch
#> ℹ Expected a ggplot object
#> ✖ Got an object of class <numeric>
Another example, to impose a data type based on a prototype:
check_cars <- function(x) vctrs::vec_assert(x, cars)
Cars <- as_assertion_factory(check_cars)
Cars() ? x <- iris
#> Error: `x` must be a vector with type:
#>
#> <data.frame<
#> speed: double
#> dist : double
#> >>
#>
#> Instead, it has type:
#>
#> <data.frame<
#> Sepal.Length: double
#> Sepal.Width : double
#> Petal.Length: double
#> Petal.Width : double
#> Species : factor<fb977>
#> >>
Or if we want to be more general:
check_valid_ptype <- function(x, ptype) vctrs::vec_assert(x, ptype)
Ptype <- as_assertion_factory(check_valid_ptype)
Ptype(cars) ? x <- iris
#> Error: `x` must be a vector with type:
#>
#> <data.frame<
#> speed: double
#> dist : double
#> >>
#>
#> Instead, it has type:
#>
#> <data.frame<
#> Sepal.Length: double
#> Sepal.Width : double
#> Petal.Length: double
#> Petal.Width : double
#> Species : factor<fb977>
#> >>
as_assertion_factory()
is
actually used to build the native assertion factories of this
package