Day 2 Session 2: Managing Side Effects
Invalid Date
At the end of this section you will be able to:
Sometimes, error messages can be cryptic:
seq[10]
Error in seq[10] : object of type 'closure' is not subsettable
An effective error condition has:
cli::cli_abort()
{cli} package (Csárdi 2022)
Prefer:
Over:
Finding simplest set of predicates is just as challenging as finding the “right” names for functions and arguments.
Content:
Formatting:
{cli} provides powerful formatting tools:
{.var y}
{.val {y}}
see cli inline-markup for more details.
A developer, calling your function, can use the class
name to handle the error, if they want.
Convention:
"{package}_error_{description}"
This “stuff” is also available to an error handler.
Provide the data that went into the predicate.
Provide name of variable, e.g. y = y
.
Avoid reserved names: message
, class
, call
, body
, footer
, trace
, parent
, use_cli_format
.
If there will be an error, surface it quickly.
Validating the arguments to a function is one way to do this.
For example:
Questions like these can be generalized into functions:
You may be familiar with match.arg()
:
rlang::arg_match()
does the same thing, but:
Designed for capturing side-effects:
dplyr::glimpse()
is a useful shortcutBe careful about accepting changes (don’t just accept).
Can be temperamental - not run on CRAN.
We will go through, using examples.
"2.2.1"
Implement validator-functions:
country
valid?btt22::btt_reset_hard("2.2.1")
Get new files, btt22::btt_get("2.2.1")
:
validate.R
test-validate.R
usethis::use_package("cli")
rlang::arg_match()
👉 devtools::load_all()
, then:
uss_get_matches("italy")
🎉uss_get_matches("tatooine")
🎉uss_get_matches("ita")
🤔get-matches.R
:
match.arg()
with rlang::arg_match()
test-get-matches.R
:
2.2.1
, then repeat 👉usethis::use_package("cli")
review validate_data_frame()
(call
argument)
build constructor for validate_cols()
:
{.field {cols}}
may be useful2.2.1
tests in test-validate.R
, as you gohold off on snapshot test (we’ll do together)
uss_make_matches()
:
matches.R
cols_engsoc()
may be usefulLeave no footprints.
Leave the global state how you found it, avoid surprises later:
Loading {devtools} in .Rprofile
changes the global state.
When we hit the “Knit” button, or the Quarto “Render” button:
.Rprofile
The {withr} package (Hester et al. 2022) gives us tools to:
Useful family of functions:
withr::local_*()
CRAN is (rightly) particular about “leave no footprints”.
You may need:
withr::local_options()
: change an optionwithr::local_dir()
: change the working directorywithr::local_tempfile()
: path to a temporary fileUseful in testthat
code and in R
code.
not_runif <- function(n, min = 0, max = 1) {
withr::local_seed(314)
runif(n, min = min, max = max)
}
usethis::use_test("validate")
change:
expect_identical(error_condition$cols_req, "foo")
to:
expect_identical(error_condition$cols_re, "foo")
rerun tests 🤔
By default, $
accepts partial matching.
We want stricter testing, so we need to set some options.
"2.2.2"
Turn off $
partial matching when testing.
btt22::btt_reset_hard("2.2.2")
Get new files, btt22::btt_get("2.2.2")
:
utils-testing.R
usethis::use_package("withr")
local_warn_partial_match()
In essence, this sets (temporarily) TRUE
:
options("warnPartialMatchDollar")
options("warnPartialMatchArgs")
options("warnPartialMatchAttr")
With a couple of wrinkles:
NULL
as FALSE
."2.2.2"
, continuedusethis::use_test("validate")
add local_warn_partial_match()
to top of blocks.
getOption("warnPartialMatchDollar")
change:
expect_identical(error_condition$cols_re, "foo")
devtools::test()
🎉
getOption("warnPartialMatchDollar")
change back to $cols_req
"2.2.3"
Implement testing with side effects
btt22::btt_reset_hard("2.2.3")
No new files.
add local_warn_partial_match()
to all test-blocks.
test-matches.R
:
expect_error()
calls for data-frame and columns (use class
argument).expect_snapshot(dplyr::glimpse(italy))
The most-common side-effect is an error. Good design:
class
using naming convention, and additional informationUse snapshot tests to capture side-effects.
Use withr::local_*()
functions to “leave no footprints”.
Brian Beckman on monads (Ian watched at least ten times, learned something each time):
tidyverse is a monoidal system
maybe monads aren’t the thing, but the functional-programming learned along the way