Section 8.1: Introduction
Section 8.2: Signalling conditions
Section 8.1: Introduction
Section 8.2: Signalling conditions
Section 8.3: Ignoring conditions
Section 8.1: Introduction
Section 8.2: Signalling conditions
Section 8.3: Ignoring conditions
Section 8.4: Handling conditions
Section 8.1: Introduction
Section 8.2: Signalling conditions
Section 8.3: Ignoring conditions
Section 8.4: Handling conditions
Section 8.5: Custom conditions
Section 8.1: Introduction
Section 8.2: Signalling conditions
Section 8.3: Ignoring conditions
Section 8.4: Handling conditions
Section 8.5: Custom conditions
Section 8.6: Applications
Section 8.1: Introduction
Section 8.2: Signalling conditions
Section 8.3: Ignoring conditions
Section 8.4: Handling conditions
Section 8.5: Custom conditions
Section 8.6: Applications
Section 8.7: Quizzes
The condition system provides a paired set of tools that allow the author of a function to:
indicate that something unusual is happening,
and the user of that function to deal with it.
The function author signals conditions with functions like stop()
(for errors), warning()
(for warnings), and message()
Then the function user can handle them with functions like tryCatch()
and withCallingHandlers()
There are three conditions that can be signaled in code:
Errors
: They indicate that the function cannot execute at its current state.
Warnings
: Indicates that something has gone wrong, but that the function has kind of recovered (or has it?????)
Messages
: They give more information of actions that are performed on behalf of the user.
stop("Hi there, I am an error.")
## Error in eval(expr, envir, enclos): Hi there, I am an error.
warning("Hi there, be careful when you see me")
## Warning: Hi there, be careful when you see me
message("I am better than them, aint I?")
## I am better than them, aint I?
In base R, errors are signalled by stop()
.
my_function1 <- function()stop("Error! Error! Error!")my_function1()
## Error in my_function1(): Error! Error! Error!
my_function1 <- function() stop("Error! Error! Error!", call. = FALSE)my_function1()
## Error: Error! Error! Error!
In rlang, they are signalled by rlang::abort()
my_function3 <- function()rlang::abort("rlang's error handling")my_function3()
## Error: rlang's error handling
You cannot have multiple error signals in one function.
my_function123 <- function(){ stop("Error! Error! Error!") stop("Error! Error! Error!", call. = FALSE) rlang::abort("rlang's error handling")}my_function123()
## Error in my_function123(): Error! Error! Error!
Indicates that something has gone wrong, but the function has kind of recovered.
Unlike errors, you can have multiple warnings from a single function call.
my_function4a <- function(){ cat("R4DS Cohort 3") warning("I am the first warning") cat("R4DS Cohort 3 again") warning("I am the second warning")}my_function4a()
## R4DS Cohort 3
## Warning in my_function4a(): I am the first warning
## R4DS Cohort 3 again
## Warning in my_function4a(): I am the second warning
By default, warnings are cached and printed only when control returns to the top level.
One can control this behaviour with the warn
option
options(warn = 0)
: defaultoptions(warn = 1)
: makes warnings appear immediately.options(warn = 1)my_function4b <- function(){ cat("R4DS Cohort 4") warning("I am the first warning")cat("R4DS Cohort 4") warning("I am the second warning")}my_function4b()
## R4DS Cohort 4
## Warning in my_function4b(): I am the first warning
## R4DS Cohort 4
## Warning in my_function4b(): I am the second warning
options(warn = 2)
: turns warnings into errorsWe can set warnings in rlang with
rlang::warn()
my_function4c <- function() rlang::warn("This is also a warning")my_function4c()
## Warning: This is also a warning
Some warnings should be more of errors than warnings
my_vec1 <- c(15, 16, 35, "50+")as.numeric(my_vec1)
## Warning: NAs introduced by coercion
## [1] 15 16 35 NA
my_vec2 <- c(15, 16, 35, "5 7")as.numeric(my_vec2)
## Warning: NAs introduced by coercion
## [1] 15 16 35 NA
Messages are signalled by message()
.
They are informational; use them to tell the user that you’ve done something on their behalf.
Use messages to provide just enough information so the user knows what’s going on, but not so much that they’re overwhelmed
library(rKenyaCensus)
Generally any function that produces a message should have some way to suppress it.
One can use suppressMessages()
Comparison between cat()
and message()
cat()
: used when the primary role of the function is to print to the console. Used when the user asks for something to be printed.
message()
: as a side-channel to print to the console when the primary purpose of the function is something else. Used when the developer elects to print something.
my_func5 <- function(){ cat("I want my function to print this") message("This function contains a message.")}my_func5()
## I want my function to print this
## This function contains a message.
The simplest way of handling conditions in R is to simply ignore them:
try()
.
try()
allows execution to continue even after an error has occurred.
my_func6a <- function(x,y){ x+y cat("Nairobi is the capital city of Kenya")}my_func6a(2)
## Error in my_func6a(2): argument "y" is missing, with no default
my_func6b <- function(x,y){ try(x+y) cat("Nairobi is the capital city of Kenya")}my_func6b(2)
## Error in try(x + y) : argument "y" is missing, with no default## Nairobi is the capital city of Kenya
Unlike errors, messages and warnings don’t terminate execution.
suppressWarnings()
suppressWarnings(warning("I am a warning, but I am just about to be supressed"))
suppressMessages()
suppressMessages({ message("I am a message, but I am just bout to be supressed") 6^2 })
## [1] 36
Every condition has default behaviour:
errors stop execution and return to the top level
warnings are captured and displayed in aggregate
messages are displayed immediately
Condition handlers allow us to temporarily override or supplement the default behaviour.
tryCatch()
and withCallingHandlers()
allow us to register handlers, functions that take the signalled condition as their single argument.
tryCatch()
defines exiting handlers; after the condition is handled, control returns to the context where tryCatch()
was called. This makes tryCatch()
most suitable for working with errors and interrupts, as these have to exit anyway.
withCallingHandlers()
defines calling handlers; after the condition is captured control returns to the context where the condition was signalled. This makes it most suitable for working with non-error conditions
Built-in conditions are lists with two elements:
message, a length-1 character vector containing the text to display to a user. To extract the message, use conditionMessage(cnd)
.
call, the call which triggered the condition. We extract it using conditionCall(cnd)
.
cnd <- rlang::catch_cnd(stop("This is an error!!"))str(cnd)
## List of 2## $ message: chr "This is an error!!"## $ call : language force(expr)## - attr(*, "class")= chr [1:3] "simpleError" "error" "condition"
tryCatch()
tryCatch()
registers exiting handlers, so it is used to handle error conditions.
Exiting handlers because they cause code to exit once the condition has been caught.
Syntax: tryCatch(my_code,my_error_function)
my_code
: piece of code we are working on.
my_error_function
: function that contains the code to run when an error is thrown
my_code <- function(x, y){ x+y}my_code(2,"Shel")
## Error in x + y: non-numeric argument to binary operator
The handler functions are called with a single argument, the condition object (cnd
).
my_func <- function(x, y){ tryCatch( x+y, error = function(cnd) cat("An error has occured") )}my_func(2,"Shel")
## An error has occured
withCallingHandlers()
withCallingHandlers()
sets up calling handlers: code execution continues normally once the handler returns
my_func2 <- function(){ withCallingHandlers( message("Omg!!, I now get why this book is titled 'Advanced R'"), message = function(cnd){ message("This package contains a message, please read it carefully") } )}my_func2()
## This package contains a message, please read it carefully
## Omg!!, I now get why this book is titled 'Advanced R'
my_func3 <- function(){ withCallingHandlers( as.numeric(c(2,3,"5 6") ), warning = function(cnd){ warning("A warning has been signalled!!!", call. = FALSE) } )}my_func3()
## Warning: A warning has been signalled!!!
## Warning in withCallingHandlers(as.numeric(c(2, 3, "5 6")), warning = function(cnd)## {: NAs introduced by coercion
## [1] 2 3 NA
??muffle
One of the challenges of error handling in R is that most functions generate one of the built-in conditions, which contain only a message and a call
We can create custom conditions that can contain additional metadata.
rlang::abort()
makes this very easy as you can supply a custom .subclass and additional metadata.
my_func <- function(){ as.numeric(c(34, 56, "7 13"))}my_func()
## Warning in my_func(): NAs introduced by coercion
## [1] 34 56 NA
my_func <- function(x){ withCallingHandlers( as.numeric(x), warning = function(cnd){ if(is.character(x)){ warning("There is a character value in your vector") } })}my_func(c("7 13"))
## Warning in (function (cnd) : There is a character value in your vector
## Warning in withCallingHandlers(as.numeric(x), warning = function(cnd) {: NAs## introduced by coercion
## [1] NA
Custom conditions are more likely to guide the user towards a correct fix.
However, they’re no better if you want to programmatically handle the errors: all the useful metadata about the error is jammed into a single string.
my_func <- function(x, y){ 2 + x + y}my_func(3, "3")
## Error in 2 + x + y: non-numeric argument to binary operator
my_func <- function(x, y){ if(is.character(x) | is.character(y)){ rlang::abort("One of the arguments that you have provided is a character") } 2 + x + y}my_func(3, "3")
## Error: One of the arguments that you have provided is a character
my_func <- function(x, y){ if(is.character(x) | is.character(y)){ rlang::abort(glue::glue({y}, " is a character")) } 2 + x + y}my_func(3, "3")
## Error: 3 is a character
my_func <- function(x, y){ if(is.character(x) & !is.character(y)){ rlang::abort(glue::glue({y}, " is a character input.")) }else if(is.character(y) & !is.character(x)){ rlang::abort(glue::glue({y}, " is a character input.")) }else if(is.character(x) & is.character(y)){ rlang::abort(glue::glue({x}, " and " ,{y}, " are characters inputs.")) } 2 + x + y}my_func("3", "10")
## Error: 3 and 10 are characters inputs.
fail_with <- function(x,y, value = NULL) { tryCatch( error = function(cnd) value, x * y )}fail_with(2,"3", "You must have insert a character value")
## [1] "You must have insert a character value"
do_with <- function(x,y, value = NULL) { tryCatch( error = function(cnd) value, { x * y cat("Your inputs are perfect\n\n") } )}do_with(2,3, "You must have insert a character value")
## Your inputs are perfect
Keyboard shortcuts
↑, ←, Pg Up, k | Go to previous slide |
↓, →, Pg Dn, Space, j | Go to next slide |
Home | Go to first slide |
End | Go to last slide |
Number + Return | Go to specific slide |
b / m / f | Toggle blackout / mirrored / fullscreen mode |
c | Clone slideshow |
p | Toggle presenter mode |
t | Restart the presentation timer |
?, h | Toggle this help |
Esc | Back to slideshow |