Chapter 8 Conditions

8.3 Ignoring conditions

I can’t quite think of a time where try is more appropriate than tryCatch - does anyone have an example?

It seems that try is just a wrapper for tryCatch and you can use it whenever you’d use try

When would you actually use suppressWarning? Maybe when loading libraries? I created an example for suppressMessages, but does someone have a better, practical use case?

As a function user, it’s common to suppress warnings when loading in data and using readr and dplyr - but not so much as a function creator

8.4.2 Exiting handlers

The protected code is evaluated in the environment of tryCatch(), but the handler code is not, because the handlers are functions.

Clarification question what is the “handler code” and “protected code”?

The protected code is inside the {} and the handler code is message = function(cnd) "There"

In the tryCatch example where we have finally print “Thank God for Beer” I find it interesting that this is printed before the code inside the tryCatch. Can anyone explain why?

From the help it’s, “expression to be evaluated before returning or exiting.” That means it’s the final thing that happens inside the tryCatch, but it happens before the return (of NA or the string), so it happens first.

8.4.3 Calling handlers

How would you define bubbling up?

Bubbling up: By default, a condition will continue to propagate to parent handlers, all the way up to the default handler (or an exiting handler, if provided)

I would consider what is being described there “bubbling up”.

Why is this message executed once per message in the function?

#> Caught a message!
#> Someone there?
#> Caught a message!
#> Why, yes!

withCallingHandlers could be understood as: “for each {message}, do x”

The following prints the message once:

## Warning in withCallingHandlers(message = function(cnd) cat("Caught a message!
## \n"), : Someone there?
## Caught a message!
## Why, yes!

and it comes in the warning message, so it’s not really a “direct” print

Oh wait, the handler code is named message, so is it possible that the two message calls in the protected block are actually calling both base::message and the message - named handler?

The return value of a calling handler is ignored because the code continues to execute after the handler completes; where would the return value go? That means that calling handlers are only useful for their side-effects.

Can we come up with an example for this masking? I think seeing it will help me understand…

This just means that you cannot capture the return value of your handler (but you sort of can)

## No, you're never gonna get it
## [1] "not this time"

How does muffling differ from suppressWarnings?

muffling allows for an over-ride/replacement of messages, while suppress just quiets everything:

Now we can use a new definition of my_particular_msg to replace “Beware”

## Dealt with this particular message
## On your guard!
## [1] "foobar"

whereas suppressMessages just returns foobar:

## [1] "foobar"

8.4.5.2 Exercises

In the example

The first three calls to show_condition make sense to me, and I even understand that the first time the code is evaluated inside a tryCatch it exists (that’s why it returns message) but how are you supplying 3 arguments to a function that just takes on one argument, code? Is that what the {} are for?

{} let us execute multiple lines of code!

8.4.5.3 Exercises

I couldn’t follow the manual’s answer for what’s happening here, can we come up with our own answer for what’s happening here in words?

## b
## a
## b
## c

The first call to withcallinghandlers adds a condition handler for conditions with class “message” to the handler stack (not sure if its actually a stack?) and then executes the second withcallinghandlers which adds another condition handler for conditions with class “message” to the handler stack (see above), and then executes the code message("c").

What happens next is a chain reaction of handlers. The call message("c") is handled by the inner handler, which then calls message("a"), but message("a") is caught by the outer handler, and so it outputs b first.

Then the inner handler resolves itself and outputs a and then that condition (having not been muffled) “bubbles up” to the outer handler, which calls message("b") again producing the second b and again since the message wasn’t muffled, the original condition message("c") “bubbles up” to the top where it is evaluated producing the output c

Another exercise: Guess the output of these two functions:

## 1. b
## 1. a
## 3. b
## 1. c
## 3. b
## 2. a
## 4. b
## 1. c

8.6.3 Resignal

The function warning2error captures an expression which is evaluated by withCallingHandlers where you have defined a handler for warning conditions. The handler captures the condition cnd raised by warn which is structure(list(message = "Hello"), class = c("warning", "condition")

The function conditionMessage is an s3 generic which evaluates to conditionMessage.condition which simply accesses cnd$message this is then the input to abort which raises an error with the message "Hello"

8.6.4 Record

If we remove cnd_muffle we see that a, b, and c are printed to the console prior to getting the conds output.

Would it be possible in the second Record example to create a function that doesn’t require us to put the abort statement at the end, just ignoring it? Or because abort is an exiting handler it needs to be last?

I’m not positive, but I don’t think so without mucking around in the C code. You “can” using try instead of tryCatch, but I can’t think of a way to have it let you try each line in your passed in expression for example

Signal is a general function that calls abort, inform or warn. It has the same signature as those functions except for the class argument that is necessary for signal but NULL by default for each of the others

If you create a condition object by hand, and signal it with signalCondition(), cnd_muffle() will not work. Instead you need to call it with a muffle restart defined, like this: withRestarts(signalCondition(cond), muffle = function() NULL)

Where does this code go given the prior example?

We would replace the code cnd_muffle(cnd):

[info] "Hello"

8.6.6.2 Exercises

Calling handlers are called in the context of the call that signaled the condition. Exiting handlers are called in the context of the call to tryCatch().

What exactly does this mean? tryCatch evaluates what we were calling the protected code first and calling handlers execute the handling code first? Can we make a simple example?

  • tryCatch is a project manager who oversees everything and then personally hands over the end product
  • withCallingHandlers writes some procedures/guidelines and assumes everyone has enough information to get their jobs done

Comparing:

##      █
##   1. ├─base::withCallingHandlers(...)
##   2. ├─global::f()
##   3. │ └─global::g()
##   4. │   └─global::h()
##   5. │     └─base::message("!")
##   6. │       ├─base::withRestarts(...)
##   7. │       │ └─base:::withOneRestart(expr, restarts[[1L]])
##   8. │       │   └─base:::doWithOneRestart(return(expr), restart)
##   9. │       └─base::signalCondition(cond)
##  10. └─(function (cnd) ...
##  11.   └─lobstr::cst()

withCallingHandlers is run to completion before f is called/put onto the stack. While f is in progress, g needs to be called so g is put on top of the stack, then h etc. Eventually they’re all completed and taken off the stack (in reverse order). Then the handler (function (cnd) ... is called

While

##     █
##  1. └─base::tryCatch(f(), message = function(cnd) lobstr::cst())
##  2.   └─base:::tryCatchList(expr, classes, parentenv, handlers)
##  3.     └─base:::tryCatchOne(expr, names, parentenv, handlers[[1L]])
##  4.       └─value[[3L]](cond)
##  5.         └─lobstr::cst()

tryCatch is still on the stack when the handler is called i.e. value, tryCatchOne, tryCatchList and tryCatch are all still in line to be completed

Calling handlers are called in the context of the call that signaled the condition, in this case,f(). So the message handler returns a value to the environment where f() is a meaningful call. Exiting handlers are called in the context of the call to tryCatch(). The exiting handler returns to an ongoing tryCatch() so it can do whatever it needs to do.

8.6.6.4 Exercises

There’s no way to break out of the function because we’re capturing the interrupt that you’d usually use!

What does this mean? You can’t stop the function if you set an interrupt argument inside a tryCatch?

That’s exactly it - this was a warning not to use inturrupt in your functions!ß

Slides

What environment(s) these restarts are called within, or if that even applies or matters

Note: for this example I set my Environment panel in Rstudio to “Manual Refresh Only” (the curly arrow menu) while running this to make sure it wasn’t doing anything to confuse me.

You can run it yourself (and then invokeRestart("force_positive")) to see the results, but to summarize:

  • The withCallingHandlers part is executing in exactly the same environment as the function.
  • The force_positive restart is executing in its own new environment.
  • The parent of that environment is the execution environment of the main function.

The caller_env for force_positive, though, is still a bit of a mystery. Some further poking found that its grandparent is the base package’s namespace, but I don’t grok why. Maybe because it’s the browser() environment? Is that a thing?

Are there any default restarts include in base R so that you could invokeRestart("XXX") or is the restart always user defined?

From the warning documentation:

While a warning is being processed, a muffleWarning restart is available. If this restart is invoked with invokeRestart, then warning returns immediately.

We can also use the computeRestarts function which lists all available “default” restart functions:

[[1]]
<restart: abort >

It seems computeRestarts() is able to find abort even without {rlang} attached! Which means it surely would be able to find some function from the base packages if one existed. But it does not, so that leads me to believe that they aren’t any beyond muffleWarning. it seems, in general, you need to specify a function name for invokeRestart()

What environment(s) these restarts are called within, or if that even applies or matters

Given this little example:

## [1] "parent in mid: "
## <environment: 0x7fc5cc2bf000>
## [1] "current in mid: "
## <environment: 0x7fc5cc2bf7e0>
## [1] "parent in low: "
## <environment: R_GlobalEnv>
## [1] "current in low: "
## <environment: R_GlobalEnv>
## [1] "parent in mid: "
## <environment: 0x7fc5cc29fd48>
## [1] "current in mid: "
## <environment: 0x7fc5cd953210>
## [1] "parent in low: "
## <environment: R_GlobalEnv>
## [1] "current in low: "
## <environment: R_GlobalEnv>
## [1] "parent in mid skip: "
## <environment: 0x7fc5cd953210>
## [1] "current in mid skip: "
## <environment: 0x7fc5cf245658>
## [1] "parent in mid: "
## <environment: 0x7fc5cf24a858>
## [1] "current in mid: "
## <environment: 0x7fc5cf24a0e8>
## [1] "parent in low: "
## <environment: R_GlobalEnv>
## [1] "current in low: "
## <environment: R_GlobalEnv>
## [[1]]
## [[1]][[1]]
## [1] "CORRECT"
## 
## 
## [[2]]
## [[2]][[1]]
## <environment: 0x7fc5cf245658>
## 
## 
## [[3]]
## [[3]][[1]]
## [1] "CORRECT"

So, the parent environment of the handler (is that the right term?) called skip_text in the mid-level function is the environment of the expr part of withRestarts (i.e. the first parameter), also in the mid-level function.

Are there any default restarts include in base R so that you could invokeRestart("XXX") or is the restart always user defined?

From the warning documentation:

While a warning is being processed, a muffleWarning restart is available. If this restart is invoked with invokeRestart, then warning returns immediately.

We can also use the computeRestarts function which lists all available “default” restart functions:

[[1]]
<restart: abort >

It seems computeRestarts() is able to find abort even without {rlang} attached! Which means it surely would be able to find some function from the base packages if one existed. But it does not, so that leads me to believe that they aren’t any beyond muffleWarning. it seems, in general, you need to specify a function name for invokeRestart()

Let’s revisit my example from the talk: can we build on this to use tidyeval so that the user can write beer_states %>% beer_mean(state) [I think we need to use .data and note how state is given as an object so we need to use tidyeval to suppress it’s evaluation…]

[1] 806.4551