There are many approaches to handling errors while writing code, if you are creating a library that will be used by many users or you simply want the code you have created not to stop processing when it hits an error, then the try()
and tryCatch()
functions in R are your friends. In this blog entry, I will be focusing on the tryCatch()
function even though what I present can be done using the try()
function with subsequent conditionals.
I like to think of the tryCatch()
function as a filter that prevents certain things from happening, if you imagine that you are running a process over many items and this process takes along time, the last thing you need is for the process to fail and have to manually start the process again or even worse restart the whole show from the beginning. This happens in real situations more than we would like, so if the show must go on, then tryCatch()
is your friend. It can act as a filter that keeps errors from stopping your fun. Like St Peter at the gates of heaven, it can keep the bad stuff out.
Now the technique I am describing here takes the filtering effect of the tryCatch()
function and gives it steroids. Imagine that you have two functions, one acting as the wrapper for the second. The inner function is a bit like purgatory - anything can happen there, good stuff and bad stuff. When the function is exited it tries to go to the outer function which is heaven. At the gates stands St Peter and if the item is an error he says “You shall not pass!” (I think that may have been someone else).
Now lets say you create a function that you would like to run called Journey()
.
Journey = function(bDidFrodoMakeIt)
{
if(!bDidFrodoMakeIt)
{
stop("Frodo cannot return to The Shire")
}
return(bDidFrodoMakeIt)
}
Evidently, Journey()
throws an error if bDidFrodoMakeIt
is false. We could construct a tryCatch()
block around every Journey()
call but maybe instead of just Journey()
we have lots of different functions, e.g. JourneyToHelmsDeep()
, JourneyToRivendale()
, JourneyToRohan()
the list goes on. I certainly don’t want to spend my time writing tryCatch()
for each of those function calls especially if the errors are all handled in a similar way.
Now the wise among you will say “one ring to rule them all”, which is precisely how we tackle this. We introduce another function called MyPrecious()
.
MyPrecious = function(sFrodo, ...)
{
funFrodo = get(sFrodo, mode = "function")
bToTheShire = tryCatch(funFrodo(...), error = function(sError){
sError = "Oh no Sauron has won, Frodo died in Mordor!"
cat(sError, "\n")
return(sError)
})
return(bToTheShire)
}
MyPrecious()
acts as an interface using get()
. It will run any string entered in the string argument sFrodo as a function as long that function exists; the arguments are passed in the ellipsis. Any errors in the call are taken care of in the tryCatch()
block. To run the Journey()
function we write:
Ending <- MyPrecious(sFrodo = "Journey", bDidFrodoMakeIt = FALSE)
# Oh no Sauron has won, Frodo died in Mordor!
Note that at this point the tryCatch()
has caught the error and prevented it from stopping a running process; the print is just a result of what we cat()
to the interpreter.
In reality though, R’s error message is often very informative and we would probably like to pass that error message through, so MyPrecious()
becomes.
MyPrecious = function(sFrodo, ...)
{
funFrodo = get(sFrodo, mode = "function")
bToTheShire = tryCatch(funFrodo(...), error = function(sError)
{
sError = paste(paste(sError$call)[1], sError$message, sep = " : ")
cat(sError, "\n")
return(sError)
})
return(bToTheShire)
}
And our print becomes
Ending = MyPrecious(sFrodo = "Journey", bDidFrodoMakeIt = FALSE)
# funFrodo : Frodo cannot return to The Shire
That’s great but why is this important? It is important because, you don’t have to create separate error handling functions at every instance or even for every function. You can confidently access the JourneyToHelmsDeep()
function in the same way
MyPrecious(sFrodo = "JourneyToHelmsDeep", ...)
No need to write any more error handling, at worst if you need different error handling for some functions, simply write MyPrecious()
equivalents for them.
Error handling is very useful when writing a program that will do serious computation or code that will be used often or by people other than yourself. It is clear that the tryCatch()
and get()
combination shown here is useful.
There needs to be real thought as to what should and should not be permitted to fail in any process design and what should happen when these events arise.
It is important to understand that writing your functions as I described above will prevent them from stopping even if you use stop()
in the inner function; if you actually need a function to fail, you should take this into consideration when writing the error handling. There will be times when you expect a failure and forget that you prevented your function from failing. Even worse is causing a silent error to occur undocumented.
With great power come great responsibility - or words to that effect.