The package name “erify” is derived from “verify” and “error”, which are the package’s two main themes.
When creating functions for other people to use, you always need some validator functions which
erify serves the exact purpose. Specifically, erify provides
Install erify from CRAN:
install.packages("erify")
Or install the development version from GitHub:
# install devtools if not
# install.packages("devtools")
::install_github("flujoo/erify") devtools
erify has some ready-to-use validator functions. Let’s have a look.
library(erify)
Suppose you are creating a function which prints a string several times to emphasize it:
# print `what` `n` times
<- function(what, n) {
emphasize for (i in 1:n) {
cat(what, "\n")
}
}
# example
emphasize("You're beautiful!", 3)
#> You're beautiful!
#> You're beautiful!
#> You're beautiful!
And suppose a novice user accidentally passes a function to argument what
, he/she will get an error message which is not very readable:
emphasize(c, 3)
#> Error in cat(what, "\n"): argument 1 (type 'builtin') cannot be handled by 'cat'
You can improve this by adding erify’s check_type()
into emphasize()
:
<- function(what, n) {
emphasize # check the type of `what`
check_type(what, "character")
# main
for (i in 1:n) {
cat(what, "\n")
}
}
emphasize(c, 3)
#> Error: `what` must have type character.
#>
#> ✖ `what` has type builtin.
In the above code, check_type(what, "character")
checks if what
has type character, and if not, generates improved error message.
You can add more validator functions. For example, suppose you want what
to be a single character, which means it must have length 1. You can check its length with check_length()
:
<- function(what, n) {
emphasize # check the type of `what`
check_type(what, "character")
# check the length of `what`
check_length(what, 1)
# main
for (i in 1:n) {
cat(what, "\n")
}
}
emphasize(c("apple", "orange"), 3)
#> Error: `what` must have length 1.
#>
#> ✖ `what` has length 2.
In the above code, check_length(what, 1)
checks if what
has length exactly 1.
Maybe this is too strict. You feel any non-empty character vector is acceptable. You can change the second argument of check_length()
in the above code:
<- function(what, n) {
emphasize # check the type of `what`
check_type(what, "character")
# check the length of `what`
check_length(what, c(0, NA))
# main
for (i in 1:n) {
cat(what, "\n")
}
}
emphasize(character(0), 3)
#> Error: `what` must have length larger than 0.
#>
#> ✖ `what` has length 0.
check_length(what, c(0, NA))
checks if what
has length larger than 0.
erify’s validator functions return silently if the argument they are checking is valid. For example,
emphasize("You're beautiful again!", 3)
#> You're beautiful again!
#> You're beautiful again!
#> You're beautiful again!
So far, you may have noticed that the error messages generated by erify’s validator functions have a consistent style.
Specifically, in this style, an error message usually has two components:
First, a general statement of the problem. For example:
#> Error: `what` must have type character.
Second, a concise description of what went wrong. For example:
#> * `what` has type builtin.
The second component may contain more items, as you will see.
This style is adopted from Hadley Wickham’s The tidyverse style guide. Check the link for more details.
You can change the error message generated by any erify’s validator function by specify arguments general
, specific
and supplement
.
For example, suppose you want an argument arg
to take only "yes"
or "no"
as input. You can put this restriction with check_content()
:
<- "I'm invalid."
arg
# check the content of `arg`
check_content(arg, c("yes", "no"))
#> Error: `arg` must be `"yes"` or `"no"`.
#>
#> ✖ `arg` is `"I'm invalid."`.
To change the default general statement of the error, you can specify argument general
. For example,
check_content(arg, c("yes", "no"), general = "You are wrong.")
#> Error: You are wrong.
#>
#> ✖ `arg` is `"I'm invalid."`.
To change the default description of the error, you can specify argument specific
. For example,
check_content(arg, c("yes", "no"), specific = "You are wrong.")
#> Error: `arg` must be `"yes"` or `"no"`.
#>
#> ✖ You are wrong.
You can add more details with argument supplement
. For example,
<- c(x = "You're wrong.", i = "But you're beautiful.")
supplement check_content(arg, c("yes", "no"), supplement = supplement)
#> Error: `arg` must be `"yes"` or `"no"`.
#>
#> ✖ `arg` is `"I'm invalid."`.
#> ✖ You're wrong.
#> ℹ But you're beautiful.
In the above code, x
means “error”, while i
means “hint”.
You can create your own validator functions with throw()
. Below is an example:
<- "You're beautiful."
general
<- c(
specifics i = "Your eyes are big.",
i = "Your hair is long.",
x = "But you broke my heart."
)
throw(general, specifics)
#> Error: You're beautiful.
#>
#> ℹ Your eyes are big.
#> ℹ Your hair is long.
#> ✖ But you broke my heart.
You can change argument as
to generate a message:
throw(general, specifics, as = "message")
#> You're beautiful.
#>
#> ℹ Your eyes are big.
#> ℹ Your hair is long.
#> ✖ But you broke my heart.
Now let’s create a validator function to check if an argument is a positive number.
<- function(x) {
check_positive check_type(x, c("integer", "double"))
check_length(x, 1)
if (is.na(x) || x <= 0) {
<- "`x` must be a positive number."
general <- "`x` is `{x}`."
specifics throw(general, specifics, env = list(x = x))
}
}
check_positive(-2)
#> Error: `x` must be a positive number.
#>
#> ✖ `x` is `-2`.
As you might have noticed, you can insert R code into general
and specifics
as {x}
in
<- "`x` is `{x}`." specifics
To execute the code, you need to pass the arguments involved to argument env
, as in
throw(general, specifics, env = list(x = x))
See glue::glue()
for more details.
join()
connects given words with a conjunction:
<- c("Pink Floyd", "Pink Freud", "Pink Florida")
x join(x, "and")
#> [1] "Pink Floyd, Pink Freud and Pink Florida"
back_quote()
convert an R object to character and add back quotations:
cat(back_quote(x))
#> `"Pink Floyd"` `"Pink Freud"` `"Pink Florida"`
back_quote(c(1, 2, NA))
#> [1] "`1`" "`2`" "`NA_real_`"
These two functions are useful to create error messages, as in the inside of check_content()
:
<- "Pink Florence"
arg check_content(arg, x)
#> Error: `arg` must be `"Pink Floyd"`, `"Pink Freud"` or `"Pink Florida"`.
#>
#> ✖ `arg` is `"Pink Florence"`.
where()
and related functions are useful for detecting where code is running. For example,
is_rmd()
#> [1] TRUE
# the code is running in a R Markdown file
where()
#> [1] "html"
# the output format is HTML
is_rstudio()
#> [1] FALSE
# not in RStudio
is_jupyter()
#> [1] FALSE
# not in a Jupyter Notebook