<- function(x) {
err_even if ((x %% 2) == 0) {stop('Even numbers are error')} else {x}
}
Catching, passing, handling errors
There’s a lot out there on handling errors. This will mostly be testing things as they come up when I need to use them. Will rely heavily on Hadley, as usual.
Returning value or the error
I want to return the output value if something works, or the error message if it doesn’t. Let’s say in a list.
I don’t like how all the demos include an explicit stop()
call, somehow that confuses me. I’m going to do essentially the same thing, but bury it in a function, so it’s more like the behaviour we’d actually see.
As a test, what does that look over a vector? I’m going to loop so it’s clearer (maybe). And so I don’t get an error about asking about a vector in an if.
<- vector(mode = 'numeric', length = 10)
outvec for (i in 1:10) {outvec[i] <- err_even(i)}
Error in err_even(i): Even numbers are error
Try and passing
If we just use try
, the error should get printed but everything keeps moving
<- vector(mode = 'numeric', length = 10)
outvec for (i in 1:10) {outvec[i] <- try(err_even(i))}
Error in err_even(i) : Even numbers are error
Error in err_even(i) : Even numbers are error
Error in err_even(i) : Even numbers are error
Error in err_even(i) : Even numbers are error
Error in err_even(i) : Even numbers are error
outvec
[1] "1"
[2] "Error in err_even(i) : Even numbers are error\n"
[3] "3"
[4] "Error in err_even(i) : Even numbers are error\n"
[5] "5"
[6] "Error in err_even(i) : Even numbers are error\n"
[7] "7"
[8] "Error in err_even(i) : Even numbers are error\n"
[9] "9"
[10] "Error in err_even(i) : Even numbers are error\n"
Huh. I thought try
just printed the values but let things keep going. Changing the non-failures to character isn’t ideal. But I guess then I’d use tryCatch
? For now though, this is exactly what i need, so I’ll stop here.
tryCatch
I actually want to capture errors, warnings, or passing to assess some code
<- function(x) {
err_even_warn5 if ((x %% 2) == 0) {
stop('Even numbers are error')
else if (x == 5) {
} warning('5 throws a warning')
else {x}
} }
I want to use this for recording, so
<- vector(mode = 'character', length = 10)
recorder for (i in 1:10) {
<- tryCatch(err_even_warn5(i),
recorder[i] error = function(c) c$message,
warning = function(c) c$message,
message = function(c) c$message)
} recorder
[1] "1" "Even numbers are error" "3"
[4] "Even numbers are error" "5 throws a warning" "Even numbers are error"
[7] "7" "Even numbers are error" "9"
[10] "Even numbers are error"
And to be even more explicit, can I do some mods in the call to just say if it passed?
<- vector(mode = 'character', length = 10)
recorder2 for (i in 1:10) {
<- tryCatch(if(is.numeric(err_even_warn5(i))) {'pass'},
recorder2[i] error = function(c) c$message,
warning = function(c) c$message,
message = function(c) c$message)
} recorder2
[1] "pass" "Even numbers are error" "pass"
[4] "Even numbers are error" "5 throws a warning" "Even numbers are error"
[7] "pass" "Even numbers are error" "pass"
[10] "Even numbers are error"
Now, for packages, sometimes I want to ignore certain warnings and messages that I know are OK, while letting other unexpected ones bubble up. I think according to Hadley here and here, I want withCallingHandlers()
rather than tryCatch because I want the code flow to proceed unimpeded.
So, let’s say I have a function that throws ‘expected’ warnings and messages and ‘unexpected’ ones, as well as errors (which should still error).
<- function(x) {
expect_unexpect if ((x == 10)) {
stop('10 is error')
else if (x == 2) {
} warning('expected warning at 2')
else if (x == 3) {
} warning('unexpected warning at 3')
else if (x == 4) {
} message('expected message at 4')
else if (x == 5) {
} message('unexpected message at 5')
else {x}
}
x }
So, how does withCallingHandlers work?
<- 1:6*NA
testvec
for (i in 1:6) {
<- withCallingHandlers(
testvec[i] warning = function(cnd) {
*2
i
}, message = function(cnd) {
* 3
i
},expect_unexpect(i)
) }
Warning in expect_unexpect(i): expected warning at 2
Warning in expect_unexpect(i): unexpected warning at 3
expected message at 4
unexpected message at 5
testvec
[1] 1 2 3 4 5 6
So that’s clearly doing what it should in that it passes the actual output, but the bits where it’s managing cnds needs work.
What I actually want to do is ignore those warnings and messages when they are expected.
What do the objects look like?
::catch_cnd(expect_unexpect(4)) rlang
<simpleMessage in message("expected message at 4"): expected message at 4
>
::catch_cnd(expect_unexpect(4))$message rlang
[1] "expected message at 4\n"
::catch_cnd(expect_unexpect(4))$call rlang
message("expected message at 4")
Do I need that? or do I just need to do a check?
This muffles warnings but not messages
<- 1:6*NA
testvec
for (i in 1:6) {
<- withCallingHandlers(
testvec[i] warning = function(cnd) {
::cnd_muffle(cnd)
rlang
},message = function(cnd) {
i
},expect_unexpect(i)
) }
expected message at 4
unexpected message at 5
testvec
[1] 1 2 3 4 5 6
But how do I muffle only some warnings? I thought I needed rlang::catch_cnd()
, but that’s an extra unnecessary layer- it’s null, the cnd being passed around here is already caught.
<- 1:6*NA
testvec
for (i in 1:6) {
<- withCallingHandlers(
testvec[i] warning = function(cnd) {
print(cnd)
print(rlang::catch_cnd(cnd))
print(cnd$message)
},message = function(cnd) {
i
},expect_unexpect(i)
) }
<simpleWarning in expect_unexpect(i): expected warning at 2>
NULL
[1] "expected warning at 2"
Warning in expect_unexpect(i): expected warning at 2
<simpleWarning in expect_unexpect(i): unexpected warning at 3>
NULL
[1] "unexpected warning at 3"
Warning in expect_unexpect(i): unexpected warning at 3
expected message at 4
unexpected message at 5
testvec
[1] 1 2 3 4 5 6
These don’t have useful classes, so just use grep on the message. When I go to do this for real, will need to use rlang::catch_cnd()
to look at what I’m dealin with and see if I can do a better condition.
<- 1:6*NA
testvec
for (i in 1:6) {
<- withCallingHandlers(
testvec[i] warning = function(cnd) {
if (grepl('^expect', cnd$message)) {
::cnd_muffle(cnd)
rlang
}
},message = function(cnd) {
if (grepl('^expect', cnd$message)) {
::cnd_muffle(cnd)
rlang
}
},expect_unexpect(i)
) }
Warning in expect_unexpect(i): unexpected warning at 3
unexpected message at 5
testvec
[1] 1 2 3 4 5 6
Asides/specific cases
For purrr::map
and similar functions, we can use purrr::safely
and purrr::possibly
to pass errors without failing a whole run. The output then needs to be unpacked and cleaned up.
For foreach::foreach
, we can use the .errorhandling
argument to pass errors through without failing the whole run.