When devising complex functions, errors or warnings can be deeply nested in internal function calls while the user-relevant call is way up the stack. In such cases, these "hook" functions facilitate the creation of error/warnings informative for the user.
generate_set_hook(namespace)
generate_stop_hook(namespace)
generate_warn_hook(namespace)
set_hook()
generate_get_hook(namespace)
stop_hook(..., msg = NULL, envir = parent.frame(), verbatim = FALSE)
warn_hook(..., envir = parent.frame(), immediate. = FALSE, verbatim = FALSE)
Character scalar giving the namespace for which the hooks are valid. Only useful when hook functions are used in a package.
Objects that will be coerced to character and will compose the error message.
A character vector, default is NULL
. If provided, this message will be displayed right under the error message. This is mostly useful when the text contains formatting because the function stop
used to send the error message erases any formatting.
An environment, default is parent.frame()
. Only relevant if the error/warning message contains
interpolation (interpolation is performed with stringmagic). It tells
where the variables to be interpolated should be found. In general you should not worry about this argument.
Logical scalar, default is FALSE
. By default the error/warning message allows variable
interpolation with stringmagic. To disable interpolation,
use verbatim = TRUE
.
Whether the warning message should be prompted directly. Defaults to FALSE
.
These functions are useful when developing complex functions relying on nested internal functions. It is important for the user to know where the errors/warnings come from for quick debugging. This "_hook" family of functions write the call of the user-level function even if the errors happen at the level of the internal functions.
If you need these functions within a package, you need to generate the set_hook
, stop_hook
and
warn_hook
functions so that they set, and look up for, hooks speficic to your function. This ensures that
if other functions outside your package also use hooks, there will be no conflict. The only thing to do
is to write this somewhere in the package files:
set_hook = generate_set_hook("pkg_name")
stop_hook = generate_stop_hook("pkg_name")
warn_hook = generate_warn_hook("pkg_name")
generate_set_hook()
: Generates a package specific set_hook
function
generate_stop_hook()
: Generates a package specific stop_hook
function
generate_warn_hook()
: Generates a package specific warn_hook
function
set_hook()
: Marks the function as the hook
generate_get_hook()
: Generates the function giving the number of frames we
need to go up the call stack to find the hooked function
warn_hook()
: Warning with a call located at a hook location
Regular stop functions with interpolation: stop_up()
. Regular argument checking
with check_arg()
and check_set_arg()
.
# The example needs to be complex since it's about nested functions, sorry
# Let's say you have an internal function that is dispatched into several
# user-level functions
my_mean = function(x, drop_na = FALSE){
set_hook()
my_mean_internal(x = x, drop_na = drop_na)
}
my_mean_skip_na = function(x){
set_hook()
my_mean_internal(x = x, drop_na = TRUE)
}
my_mean_internal = function(x, drop_na){
# simple check
if(!is.numeric(x)){
# note that we use string interpolation with stringmagic.
stop_hook("The argument `x` must be numeric. PROBLEM: it is of class {enum.bq ? class(x)}.")
}
if(drop_na){
return(mean(x, na.rm = TRUE))
} else {
return(mean(x, na.rm = FALSE))
}
}
# Let's run the function with a wrong argument
x = "five"
try(my_mean(x))
#> Error : the full stack is shown (set this off with setDreamerr_show_stack(FALSE))
#> [01] tryCatch(withCallingHandlers({
#> [02] tryCatchList(expr, classes, parentenv, handlers)
#> [03] tryCatchOne(tryCatchList(expr, names[-nh], parentenv, handlers[-nh]), names[nh], parentenv, handlers...
#> [04] doTryCatch(return(expr), name, parentenv, handler)
#> [05] tryCatchList(expr, names[-nh], parentenv, handlers[-nh])
#> [06] tryCatchOne(expr, names, parentenv, handlers[[1L]])
#> [07] doTryCatch(return(expr), name, parentenv, handler)
#> [08] withCallingHandlers({
#> [09] saveRDS(do.call(do.call, c(readRDS("C:\\Users\\berge028\\AppData\\Local\\Temp\\RtmpyexTgx\\callr-fun...
#> [10] do.call(do.call, c(readRDS("C:\\Users\\berge028\\AppData\\Local\\Temp\\RtmpyexTgx\\callr-fun-3c04235...
#> [11] (function (what, args, quote = FALSE, envir = parent.frame())
#> [12] (function (..., cli_colors, pkgdown_internet)
#> [13] pkgdown::build_site(...)
#> [14] build_site_local(pkg = pkg, examples = examples, run_dont_run = run_dont_run, seed = seed, lazy = la...
#> [15] build_reference(pkg, lazy = lazy, examples = examples, run_dont_run = run_dont_run, seed = seed, ove...
#> [16] purrr::map(topics, build_reference_topic, pkg = pkg, lazy = lazy, examples_env = examples_env, run_d...
#> [17] map_("list", .x, .f, ..., .progress = .progress)
#> [18] with_indexed_errors(i = i, names = names, error_call = .purrr_error_call, call_with_cleanup(map_impl...
#> [19] withCallingHandlers(expr, error = function(cnd) {
#> [20] call_with_cleanup(map_impl, environment(), .type, .progress, n, names, i)
#> [21] .f(.x[[i]], ...)
#> [22] withCallingHandlers(data_reference_topic(topic, pkg, examples_env = examples_env, run_dont_run = run...
#> [23] data_reference_topic(topic, pkg, examples_env = examples_env, run_dont_run = run_dont_run)
#> [24] run_examples(tags$tag_examples[[1]], env = if (is.null(examples_env)) NULL else new.env(parent = exa...
#> [25] highlight_examples(code, topic, env = env)
#> [26] downlit::evaluate_and_highlight(code, fig_save = fig_save_topic, env = child_env(env), output_handle...
#> [27] evaluate::evaluate(code, child_env(env), new_device = TRUE, output_handler = output_handler)
#> [28] evaluate_call(expr, parsed$src[[i]], envir = envir, enclos = enclos, debug = debug, last = i == leng...
#> [29] timing_fn(handle(ev <- withCallingHandlers(withVisible(eval_with_user_handlers(expr, envir, enclos, ...
#> [30] handle(ev <- withCallingHandlers(withVisible(eval_with_user_handlers(expr, envir, enclos, user_handl...
#> [31] try(f, silent = TRUE)
#> [32] tryCatch(expr, error = function(e) {
#> [33] tryCatchList(expr, classes, parentenv, handlers)
#> [34] tryCatchOne(expr, names, parentenv, handlers[[1L]])
#> [35] doTryCatch(return(expr), name, parentenv, handler)
#> [36] withCallingHandlers(withVisible(eval_with_user_handlers(expr, envir, enclos, user_handlers)), warnin...
#> [37] withVisible(eval_with_user_handlers(expr, envir, enclos, user_handlers))
#> [38] eval_with_user_handlers(expr, envir, enclos, user_handlers)
#> [39] eval(expr, envir, enclos)
#> [40] eval(expr, envir, enclos)
#> [41] try(my_mean(x))
#> [42] tryCatch(expr, error = function(e) {
#> [43] tryCatchList(expr, classes, parentenv, handlers)
#> [44] tryCatchOne(expr, names, parentenv, handlers[[1L]])
#> [45] doTryCatch(return(expr), name, parentenv, handler)
#> [46] my_mean(x)
#> [47] my_mean_internal(x = x, drop_na = drop_na)
#> [48] stop_hook("The argument `x` must be numeric. PROBLEM: it is of class {enum.bq ? class(x)}.")
#> [49] stop_up(..., up = up, msg = msg, envir = envir, verbatim = verbatim):
#> The argument `x` must be numeric. PROBLEM: it is of class `character`.
# => the error message reports that the error comes from my_mean
# and *not* my_mean_internal