最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

r - Control new line placement of base::stop() - Stack Overflow

programmeradmin1浏览0评论

I would like to write a custom error function in R that uses a more consistent formatting of error messages. In base R, errors are formatted in a way that puts error messages on a new line if it exceeds the console width and indents it by two spaces:

abort <- function(msg) stop(msg)
> abort("This is a very very very very long error message")
# Error in abort("This is a very very very very long error message") : 
#   This is a very very very very long error message

If it doesn't exceed the console width, it is put directly behind the call:

> abort("Short message")
# Error in abort("Short message") : Short message

In my custom error function, I intend to keep the formatting more consistent by always starting the error messages on a new line (similar to the cli package error format):

abort <- function(msg) stop("\n", msg)

However, when using this function with a very long error message (or a long call), the base R error formatting adds a new line and thus unwanted white space to the error output.

> abort("This is a very very very very very very very very long error message")
# Error in abort("This is a very very very very very very very very long error message") : 
#   
# This is a very very very very very very very very long error message

Is there any way to control this behavior, either by suppressing it or by finding out when it occurs to manually work around it?

I would like to write a custom error function in R that uses a more consistent formatting of error messages. In base R, errors are formatted in a way that puts error messages on a new line if it exceeds the console width and indents it by two spaces:

abort <- function(msg) stop(msg)
> abort("This is a very very very very long error message")
# Error in abort("This is a very very very very long error message") : 
#   This is a very very very very long error message

If it doesn't exceed the console width, it is put directly behind the call:

> abort("Short message")
# Error in abort("Short message") : Short message

In my custom error function, I intend to keep the formatting more consistent by always starting the error messages on a new line (similar to the cli package error format):

abort <- function(msg) stop("\n", msg)

However, when using this function with a very long error message (or a long call), the base R error formatting adds a new line and thus unwanted white space to the error output.

> abort("This is a very very very very very very very very long error message")
# Error in abort("This is a very very very very very very very very long error message") : 
#   
# This is a very very very very very very very very long error message

Is there any way to control this behavior, either by suppressing it or by finding out when it occurs to manually work around it?

Share Improve this question asked Feb 10 at 10:51 jslthjslth 3131 silver badge5 bronze badges 5
  • 1 What if you just did it without printing the call: abort <- function(msg) stop("\n", msg, call.=FALSE)? – DaveArmstrong Commented Feb 10 at 12:19
  • @SamR indeed - that's how rlang does it. I was about to post as an answer, but feel free. – Allan Cameron Commented Feb 10 at 12:30
  • @AllanCameron Wouldn't this lead to the error not being handled correctly as an error condition/restart? tryCatch(abort("error message"), error = \(e) conditionMessage(e)) would still print the "fake" error message and return an empty string as the actual error message in stop() is empty. On the other hand, tryCatch(rlang::abort("error message"), error = \(e) conditionMessage(e)) correctly catches the error message. Do you know how rlang achieves this? – jslth Commented Feb 10 at 12:44
  • @jslth You can still pass the error message if you use stop(msg) inside abort - as in my answer below. tryCatch(abort("error message"), error = \(e) conditionMessage(e)) - it gives the same result as tryCatch(rlang::abort("error message"), error = \(e) conditionMessage(e)) – Allan Cameron Commented Feb 10 at 13:14
  • @AllanCameron Ah, my bad, I was still looking at SamR's initial comment. That is elegant! – jslth Commented Feb 10 at 13:18
Add a comment  | 

2 Answers 2

Reset to default 3

It's easy enough to temporarily suspend error message printing with options(show.error.messages = FALSE), then print whatever message you like to the console. Thereafter, you can throw an error using a simple stop(msg) to halt execution, and only your own bespoke message will appear.

The fatal issue with this approach is that after the program halts, you haven't turned the user's error printing back on, which is a big problem. You can't simply reset the user options inside abort after calling stop(), since the program has halted and your option-resetting code will simply not execute.

This is where base R's on.exit() comes in. This handy function allows you to supply an arbitrary block of code to be run after your function exits, even if your function exits due to an unhandled error. We can therefore tell on.exit to reset the user's options when we exit the abort function, as long as we register the on.exit instructions before we stop() the program.

This mechanism is essentially how rlang manages to produce error messages with bespoke formatting, via the function rlang:::signal_abort.

We also want to examine the call stack so that we can tell the user where abort was encountered, so a minimal implementation might be something like:

abort <- function(msg) { 
  sc <- sys.calls()
  lsc <- length(sc)
  if(lsc == 1) header <- "Error:\n"
  if(lsc > 1) header <- paste0("Error in `", deparse(sc[lsc - 1][[1]]), "`:\n")
  old_options <- options(show.error.messages = FALSE)
  on.exit(options(old_options))
  message(header, msg)
  stop(msg)
}

Testing, we get:

abort("Abort messages print as desired") 
#> Error:
#> Abort messages print as desired

stop("Standard error messages still print normally")
#> Error: Standard error messages still print normally

And if we encounter abort inside a function, we can see execution is halted and we get some information about the call that led to the abort being triggered:

test_fun <- function() {
  abort("This is the error")
  cat("This line should not print")
}

test_fun()
#> Error in `test_fun()`:
#> This is the error

@AllanCamerons answer already answers my question in a very elegant way. However, there's one problem that kept bugging me: The function signals both a message and an error. After studying the source code of rlang::signal_abort(), I found that a mix of signalCondition() and cat() mostly mirrors the behavior of rlang::abort():

abort <- function(msg, call = sys.call(1)) {
  cnd <- errorCondition(msg, call = call)
  signalCondition(cnd)
  msg <- sprintf("Error in %s:\n- %s", deparse(call), msg)
  cat(msg, "\n", file = stderr())
  old_options <- options(show.error.messages = FALSE)
  on.exit(options(old_options))
  stop("")
}

What (I think) this does:

  • signalCondition(cnd) is where the error condition is signaled and specifies the "official" error message (as caught condition handlers) but does not abort execution.
  • cat(..., file = stderr()) prints to stderr but does not signal a message.
  • stop("") aborts execution but neither signals another error (as this is already done earlier) nor prints an error message (as it is suppressed).

Why this is useful:

  • Message conditions are not caught by a condition handler.
> tryCatch(abort("something went wrong"), message = \(e) "message caught!")
# Error in tryCatch(abort("something went wrong"), message = function(e) "message caught!"):
# - something went wrong
  • Error messages are not printed when the error condition is caught by a condition handler.
> tryCatch(abort("something went wrong"), error = \(e) "error caught!")
# [1] "error caught!"
  • Condition objects caught by condition handlers use the unformatted error message which might look less awkward in practice - depending on the format applied.
> try(abort("something went wrong"))
# Error : something went wrong
发布评论

评论列表(0)

  1. 暂无评论