R return object invisibly together with normal error when error is thrown

Viewed 94

I have a function that may throw an error. When an error is thrown, I'd like to show the error message, as if the error actually occurred, and further return an object invisibly.

I looked at this thread, which uses withCallingHandlers and logs the error message somewhere. This comes close, but I do not want to log the message as text and then print a text message, the function should show the error message as if it would have exited on error.

The functions workflow looks like the following:

foo <- function(x){

  y <- x + 1
  
    if(y == 2) {
    stop("oops")
    # also return y invisibly when error is thrown,
  }
  
  z <- y + 1
  z
  
}

Based on input x an intermediate y is calculated. y is used for the error check. If an error occurs y should be returned invisibly and an normal error message should be thrown. Otherwise z is calculated an returned.

foo(1) should return error message and y invisibly.

I thought about using on.exit but in this case, always y is return invisibly.

Any help is appreciated.

Add: Maybe what I have in mind is not possible. In this case, would it be possible to show a logged error message in a way that comes close to a real error message?

Add2: I thought about issuing a warning, but in my actual use case a warning would be misleading, since the function does not produce desired result z, but just some intermediate result y and I want to return y so that the user can inspect it further, and reason about why it wasn't processed correctly by foo. Thinking about it, other must have encountered the same problem, there should be some kind of solution.

Add3: Maybe it is possible to use on.exit together with a flag which is triggered, so that on.exit will return y invisibly in case of an error and do nothing otherwise.

2 Answers
func <- function(x) { a <- simpleError("quux"); attr(a,"abc") <- 7; stop(a); }
func()
# Error: quux

So far so good, we see an error. If we catch this and look at the contents of the error message we can see the attribute tucked inside:

dput(tryCatch(func(), error=function(e) e))
# structure(list(message = "quux", call = NULL), class = c("simpleError", 
# "error", "condition"), abc = 7)

and even extract it easily

dput(tryCatch(func(), error=function(e) attr(e,"abc")))
# 7

Below I post my final solution to my question. Basically it is based on r2evans answer combined with user20650's comment.

As r2evans showed we can use simpleError to create an error and also attach an object in the attributes. The big plus of this approach is that it returns a real error that we can program with. If we would only show the error message and then return a vector invisibly, tryCatch wouldn't recognize it as an error. The downside is that simpleError doesn't look like a normal error to an interactive user when printed in the console. It will show something like <simpleError: x must not be 1>. However, the message is not printed in red like normal errors.

Here user20650's comment shows a nice way out. We can first print the message with message(conditionMessage(e)) and then we can return the simpleError invisibly.

foo <- function(x) {
  
  y <- x + 1

  if(y == 2) {
    foo_internal <- function(val) {
      a <- simpleError("x must not be 1")
      attr(a,".data") <- y
      stop(a)
    } 
    return(
      tryCatch(foo_internal(y),
               error = function(e) {
                message(conditionMessage(e))
                invisible(e)
                })
           )
  }
  
  z <- y + 1
  z
  
}

# this is a special function to inspect the object attached to `simpleError`
inspect <- function(x = NULL) {
  if(is.null(x)) {
    x <- .Last.value
  }
  attr(x,".data")
}

# returns error message
foo(1)
#> x must not be 1

# we can get y's value with a special inspect function 
inspect()
#> [1] 2

# foo(1) returns a "real" error
class(foo(1))
#> x must not be 1
#> [1] "simpleError" "error"       "condition"

Created on 2021-03-13 by the reprex package (v0.3.0)

Related