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)

Arguments

namespace

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.

msg

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.

envir

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.

verbatim

Logical scalar, default is FALSE. By default the error/warning message allows variable interpolation with stringmagic. To disable interpolation, use verbatim = TRUE.

immediate.

Whether the warning message should be prompted directly. Defaults to FALSE.

Details

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")

Functions

  • 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

See also

Regular stop functions with interpolation: stop_up(). Regular argument checking with check_arg() and check_set_arg().

Author

Laurent Berge

Examples


# 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