Reputation: 598
I have a list of inputs over which I loop using purrr::map. Using a tweaked version of purrr::possibly (for more details see here) Applying this version of "possibly" to my main function, I'm able to use purrr::map to loop over my input list and store the results, or, alternatively, the complete error or warning messages if the function (partially) fails.
While this works fine for inputs where the function works correctly and inputs where it fails so that an error is produced, it is less optimal when a warning is produced. The resulting output from purrr::map will store only the warning, but not the result.
input <- list(10, -1 , "A")
possibly2 <- function (.f) {
.f <- purrr::as_mapper(.f)
function(...) {
tryCatch(.f(...),
error = function(e) {
return(paste0("Error: ", e$message))
}, warning = function(w) {
return(paste0("Warning: ", w$message))
}
)
}
}
safer_log <- possibly2(.f = log)
map(input, safer_log) %>%
set_names(c("1. good input", "2. warning", "3. error")) # just added for naming the list elements
This results in:
$`1. good input`
[1] 2.302585
$`2. warning`
[1] "Warning: NaNs produced"
$`3. error`
[1] "Error: non-numeric argument to mathematical function"
However, for the warnings, I would actually like to include the results as well. Similar to:
> log(-1)
[1] NaN
Warning message:
In log(-1) : NaNs produced
But I'm not really sure where the result of the main function is stored and how I could adapt the code to return it together with the warning. Probably, in cases where this happens, it is also necessary to split the output into elements $result $warning.
Upvotes: 2
Views: 424
Reputation: 11898
You need to use a custom condition handler to capture warnings while
retaining the result. That’s because tryCatch()
unwinds the call stack,
discarding the context from the lower level that signaled the condition.
Only the condition object (the warning) is available at that point.
withCallingHandlers()
is the function to use to install a custom handler.
In order to prevent default behaviour, handlers need to invoke a restart.
This can be done with tryInvokeRestart()
. warning()
makes a
muffleWarning
restart available to avoid printing the warning message.
Here I also demonstrate adding your own restart using withRestarts()
.
Our custom error handler uses that restart to avoid aborting when an error
is signaled, returning NULL
instead. But you could just as well keep
the tryCatch()
for errors.
Here’s the modified function:
catching <- function(f) {
function(...) {
error <- NULL
handleError <- function(e) {
error <<- conditionMessage(e)
tryInvokeRestart("useValue", NULL)
}
warnings <- list()
handleWarning <- function(w) {
msg <- conditionMessage(w)
warnings <<- append(warnings, msg)
tryInvokeRestart("muffleWarning")
}
result <- withCallingHandlers(
error = handleError,
warning = handleWarning,
withRestarts(f(...), useValue = identity)
)
list(result = result, error = error, warnings = warnings)
}
}
And some results:
lapply(list(1, -1, "a"), catching(log)) |> str()
#> List of 3
#> $ :List of 3
#> ..$ result : num 0
#> ..$ error : NULL
#> ..$ warnings: list()
#> $ :List of 3
#> ..$ result : num NaN
#> ..$ error : NULL
#> ..$ warnings:List of 1
#> .. ..$ : chr "NaNs produced"
#> $ :List of 3
#> ..$ result : NULL
#> ..$ error : chr "non-numeric argument to mathematical function"
#> ..$ warnings: list()
For more about R’s condition system, I found this article particularly helpful: http://adv-r.had.co.nz/beyond-exception-handling.html.
Upvotes: 3