Recently I was working on running tests for an R package in parallel. As the tests were quite complicated, I was overwhelmed by a bunch of stuff I did not care at all, for example by a warning which was by intention generated by a function, to draw attention to the fact that something important was happening. But in a test I did not care about that warning. I did not want to suppress all the warnings, just that specific one. This is about how I did it.
rlang offers a nice possibility to generate warnings of a user-defined class by using rlang::warn. (You can of course also use base R to create your own conditions, see for example tryCatchLog-intro (read the "User defined conditions" section).
a_function <- function(x)
{
if (x <= 42L)
{
rlang::warn(
message = "Something special is now happening",
class = "my_special_warning"
)
}
rlang::warn("another warning")
return(x)
}
another_function <- function(x)
{
withCallingHandlers(
expr = a_function(x),
warning = function(w)
{
if (inherits(x = w, what = "my_special_warning"))
{
tryInvokeRestart("muffleWarning")
}
}
)
}
x <- another_function(42L)
# Warning message:
# another warning
x
# 42
A few important points to notice:
- I used withCallingHandlers instead of tryCatch. For a nice summary of the differences between them see for example tryCatchLog-intro
- I used tryInvokeRestart instead of invokeRestart. More on this in a moment.
- the name "muffleWarning": this is not defined in the code above, so where does it come from?
If you read the documentation:
?warning
you might notice the following line:
While the warning is being processed, a muffleWarning restart is available. If this restart is invoked with invokeRestart, then warning returns immediately.
So if you wonder where the "muffleWarning" comes from, this is the name of the restart defined in the base code, just have a look at the definition of the warning function.
First please notice that the code above suppresses (muffles) only our special warning, the other warning passes through - as my intention was.
As a side note, rlang::warn has a possibly useful option, namely you can attach additional information to the warning, for example the time when the warning was issued.
a_function <- function(x)
{
if (x <= 42L)
{
rlang::warn(
message = "This is my special warning",
class = "my_special_warning",
event_time = Sys.time()
)
}
}
another_function <- function(x)
{
withCallingHandlers(
expr = a_function(x),
warning = function(w)
{
if (inherits(x = w, what = "my_special_warning"))
{
str(w)
tryInvokeRestart("muffleWarning")
}
}
)
}
another_function(42L)
#List of 2
#$ message : chr "This is my special warning"
#$ event_time: POSIXct[1:1], format: "2021-02-03 19:28:02"
#- attr(*, "class")= chr [1:3] "my_special_warning" "warning" "condition"
Now, I promised I'll get back to the restarts. A restart, in my understanding, is a (mostly user-defined) function which is executed
when a certain condition (message/warning/error) happens (wouldn't it be nice to have something similar in real life?)
Here is an example:
a_function <- function(x)
{
withRestarts(
expr =
{
message("x: ", x)
if (x < 42)
{
stop("On a deep level, I know I'm not up to this task. I'm so sorry")
}
print(x)
warning("you think you're done?")
},
my_first_restart = function()
{
message("this is my_first_restart")
},
my_second_restart = function()
{
message("this is my_second_restart")
}
)
}
another_function <- function(x)
{
withCallingHandlers(
expr =
{
message("before")
a_function(x)
message("after")
},
error = function(e)
{
tryInvokeRestart("my_first_restart")
},
warning = function(w)
{
tryInvokeRestart("my_second_restart")
}
)
}
another_function(41L)
# before
# x: 41
# this is my_first_restart
# after

As you can see, my_first_restart is called when the error occurs in a_function, then we continue directly after the call of a_function inside another_function.
another_function(420L)
# before
# x: 420
# [1] 420
# this is my_second_restart
# after
In this case, we have not an error, but a warning, so my_second_restart is called. Funny enough, you can play with as many restarts as you want. If you define your own classes for the warnings or for the errors, as shown in the initial example, you can go as many paths you want.
Test to check if you paid attention: remember that I used tryInvokeRestart instead of invokeRestart? Do you think that's relevant? The short answer: yes, that's important. The documentation
?tryInvokeRestart
says:
tryInvokeRestart is a variant of invokeRestart that returns silently when the restart cannot be found with findRestart...
Only use invokeRestart when you have control of the signalling context, or when it is a logical error if the restart is not available.
So it is recommended to make yourself a favor and always use tryInvokeRestart. Here is an example to show the impact: note that I now use invokeRestart, and I have a typo in the name of the restart.
a_function <- function(x)
{
withRestarts(
expr =
{
message("x: ", x)
if (x < 42)
{
stop("On a deep level, I know I'm not up to this task. I'm so sorry")
}
print(x)
},
my_first_restar = function()
{
message("this is my_first_restart")
}
)
}
another_function <- function(x)
{
withCallingHandlers(
expr =
{
message("before")
a_function(x)
message("after")
},
error = function(e)
{
invokeRestart("my_first_restart")
}
)
}
another_function(41L)
# before
# x: 41
# Error in invokeRestart("my_first_restart") :
# no 'restart' 'my_first_restart' found
Got it?
So be polite and use tryInvokeRestart when working with restarts.
Have the feeling that this is too much info? That you'll never be able to remember all these details? Don't worry (too much). A saying I like to remind myself in these situations: there will always be somebody more clever than me, a better coder than me, a faster runner... (fill in your dots). This does not matter. What matters is to try to be a little bit better than myself yesterday.
Don't let yourself get overwhelmed.
A very dear friend of mine has a very difficult job, in which many persons depend on her help. I asked her one day how did she manages to get up every day. And I send her a link of an artist, with a drawing for children: a boy a in green dragon costume at the toes of a huge... something, just look at the picture, you'll see what I mean. I told my friend that sometimes the world looks like this huge something to me. As I asked her how she manages, she replied smiling: I start with one toe.
Make a promise. Show up. Do the work. Repeat.