8 Conditions

8.1 Signalling conditions

  1. Q: Write a wrapper around file.remove() that throws an error if the file to be deleted does not exist.

    A: We have several options here. However, we prefer the following solution for its clarity and simplicity:

  2. Q: What does the appendLF argument to message() do? How is it related to cat()?

    A: The appendLF argument automatically appends a new line to the message. Let’s illustrate this behaviour with a small example function:

    Compared to cat(), message() adds the newl line for you.

8.2 Handling conditions

  1. Q: What extra information does the condition generated by abort() contain compared to the condition generated by stop()? i.e. what’s the difference between these two objects? Read the help for ?abort to learn more.

    A: In contrast to stop(), which contains the call, abort() stores the whole backtrace generated by rlang::trace_back(). This is a lot of extra data!

  2. Q: Predict the results of evaluating the following code

    A: The first three examples are straightforward:

    The last example is the most interesting and makes us aware of the exiting qualities of tryCatch(), it will terminate the evaluation of the code as soon as it is called.

  3. Q: Explain the results of running this code:

    A: It’s a little tricky to untangle the flow here:

    First, message("c") is run, and it’s caught by (1). It then calls message("a"), which is caught by (2), which calls message("b"). message("b") isn’t caught by anything, so we see a b on the console, followed by a. But why do we get another b before we see c? That’s because we haven’t handled the message, so it bubbles up to the outer calling handler.

  4. Q: Read the source code for catch_cnd() and explain how it works. At the time the book was written, the source for catch_cnd() was a little simpler:

    A: catch_cnd() is a simple wrapper around tryCatch. If a condition is signalled, it’s caught and returned. If no condition is signalled, execution proceeds sequentially and the function returns NULL.

    The current version of catch_cnd() is a little more complex because it allows you to specify which classes of condition you want to capture. This requires some manual code generation because the interface of tryCatch() provides condition classes as argument names.

  5. Q: How could you rewrite show_condition() to use a single handler?

    A: Let’s use the condition argument of tryCatch as shown in rlang::catch_cond() above:

8.3 Custom conditions

  1. Q: Inside a package, it’s occasionally useful to check that a package is installed before using it. Write a function that checks if a package is installed (with requireNamespace("pkg", quietly = FALSE)) and if not, throws a custom condition that includes the package name in the metadata.

    A:

  2. Q: Inside a package you often need to stop with an error when something is not right. Other packages that depend on your package might be tempted to check these errors in their unit tests. How could you help these packages to avoid relying on the error message which is part of the user interface rather than the API and might change without notice?

    A: Instead returning an error it might be preferable to throw a customized condition and place a standardized error message inside the metadata. Then the downstream package could check for the class of the condition, rather than inspecting the message.

8.4 Applications

  1. Q: Create suppressConditions() that works like suppressMessages() and supressWarnings() but suppresses everything. Think carefully about how you should handle errors.

    A: In general we would like to catch errors, since they contain important information for debugging. In order to suppress the error message and hide the returned error object from the console, we handle errors within a tryCatch() and return the error object invisibly:

    After we defined the error handling, we can just combine it with the other handlers to create suppressConditions():

    To test the new function we apply it to a set of conditions and inspect the returned error object.

  2. Q: Compare the following two implementations of message2error(). What is the main advantage of withCallingHandlers() in this scenario? (Hint: look carefully at the traceback.)

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

    As seen above, the use of withCallingHandlers() returns more information and points us to the exact call in our code:

  3. Q: How would you modify the catch_cnds() defined if you wanted to recreate the original intermingling of warnings and messages?

    A: To preserve the original order, we have to capture everything into a single list. This makes using this function slightly harder since the caller is responsible for handling the different condition classes.

  4. Q: Why is catching interrupts dangerous? Run this code to find out.

    A: When running the bottles_of_beer() function in your console, the output should look somehow similar to the following:

    At this point you’ll probably recognise how hard it is to get the number of bottles down from 99 to 0. There’s no way to break out of the function because we’re capturing the interrupt that you’d usually use!