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 allows to append a new line to the message. Let’s illustrate this behaviour with a small example function:

    This is very similar to the fill argument in cat():

    Alternatively, you can also create a new line by adding \n to the end of the message string.

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():

  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: Lets look at the inner withCallingHandlers() first: The first message printed by this statement would be a, so the outer withCallingHandlers prints b (1). Afterwards the message of the inner handler gets printed: a (2). Next the inner withCallingHandlers() would print c, so the outer withCallingHandlers() prints again b (3). Finally c (4) is printed.

  4. Q: Read the source code for catch_cnd() and explain how it works.

    A: catch_cnd basically wraps tryCatch and returns the default result. The expression is evaluated and it is ensured that NULL is returned, when there is no signalling condition (instead of the expression’s return value). catch_cnd returns a list with the the first condition caught (containing message and call) or NULL.

  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: It’s quite tedious to build up this behaviour on top of requireNamespace(). However, if all arguments are supplied correctly, requiredNamespace()’s return behaviour can be summarised in the following way:

    • when the namespace has already been loaded, TRUE is returned
    • when the namespace is not yet loaded:
      • a message is thrown
      • depending on further success or failure TRUE or FALSE is returned.

    Therefore, we change the behaviour of requireNamespace for missing packages and we return the error condition:

    Now we can test our new function for all cases:

  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. This message would remain stable and also won’t be affected by automatic translations.

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: The output of catch_cnds() should be returned in the order: messages, errors, warnings. To mimic the orignal behaviour we can sort the output accordingly:

  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. The underlying issue lies in the usage of interrupts to trigger the count of beers. Interrupts to update the beer count, need to occur within the bottles_of_beers() function’s sys.sleep() part in the tryCatch() block. Only there the intended special behaviour of interrupts triggers the update of the beer count. In cases of interrupts during the execution of other parts of the bottles_of_beer() function, these will just exit the function, as interrupts typically do in R. To make this behaviour more apparent, the following definition of bottles_of_beer() returns an explicit message, emphasising where the interrupt occured: