112

I hope to know why message() is a better choice than print() when it comes to printing diagnostic messages.

For example, the print() function is a better choice to print R object such as 'iris', whereas, message() is better when we want to concatenate strings e.g. message("a", "b") is shorter than print(paste0("a", "b")).

However, I think there are more differences than those simple ones listed above. I have read the documentation for both methods

but, it seems they are not as informative as I had hoped to my question.

I would appreciate if someone let us know in which case message() is better than print(), and why.

coatless
  • 20,011
  • 13
  • 69
  • 84
Kim
  • 1,768
  • 2
  • 14
  • 24
  • 2
    @MartinMorgan, I mostly agree with this, but `message()` also signals a "message", which is what `suppressMessages()` captures. `suppressMessages()` don't suppress pure stderr output, e.g. `suppressMessages(cat("hello\n", file=stderr()))` still displays `hello` in the console. – HenrikB Apr 19 '16 at 03:40
  • @HenrikB `stop`, `warning`, `message` all signal conditions, which is what makes them catch / suppress-able `tryCatch(message("hello"), message=force)`; `cat(file=stderr())` is poor style (and ineffective, as your example illustrates!) if the intention is to signal a diagnostic condition. – Martin Morgan Apr 19 '16 at 08:22

1 Answers1

198

TL;DR

You should use cat() when making the print.*() functions for S3 objects. For everything else, you should use message() unless the state of the program is problematic. e.g. bad error that is recoverable gives warning() vs. show stopping error uses stop().

Goal

The objective of this post is to provide feedback on the different output options a package developer has access to and how one should structure output that is potentially on a new object or based upon strings.

R Output Overview

The traditional output functions are:

  1. print()
  2. cat()
  3. message()
  4. warning()
  5. stop()

Now, the first two functions (print() and cat()) send their output to stdout or standard output. The last three functions (message(), warning(), and stop()) send their output to stderr or the standard error. That is, the result output from a command like lm() is sent to one file and the error output - if it exists - is sent to a completely separate file. This is particularly important for the user experience as diagnostics then are not cluttering the output of the results in log files and errors are then available to search through quickly.

Designing for Users and External Packages

Now, the above is framed more in a I/O mindset and not necessarily a user-facing frameset. So, let's provide some motivation for it in the context of an everyday R user. In particular, by using 3-5 or the stderr functions, their output is able to be suppressed without tinkering with the console text via sink() or capture.output(). The suppression normally comes in the form of suppressWarnings(), suppressMessages(), suppressPackageStartupMessages(), and so on. Thus, users are only confronted with result facing output. This is particularly important if you plan to allow users the flexibility of turning off text-based output when creating dynamic documents via either knitr, rmarkdown, or Sweave.

In particular, knitr offers chunk options such as error = F, message = F, and warning = F. This enables the reduction of text accompanying a command in the document. Furthermore, this prevents the need from using the results = "hide" option that would disable all output.

Specifics of Output

print()

Up first, we have an oldie but a goodie, print(). This function has some severe limitations. One of them being the lack of embedded concatenation of terms. The second, and probably more severe, is the fact that each output is preceded by [x] followed by quotations around the actual content. The x in this case refers to the element number being printed. This is helpful for debugging purposes, but outside of that it doesn't serve any purpose.

e.g.

print("Hello!")

[1] "Hello!"

For concatenation, we rely upon the paste() function working in sync with print():

print(paste("Hello","World!"))

[1] "Hello World!"

Alternatively, one can use the paste0(...) function in place of paste(...) to avoid the default use of a space between elements governed by paste()'s sep = " " parameter. (a.k.a concatenation without spaces)

e.g.

print(paste0("Hello","World!"))

[1] "HelloWorld!"

print(paste("Hello","World!", sep = ""))

[1] "HelloWorld!"

cat()

On the flip side, cat() addresses all of these critiques. Most notably, the sep=" " parameter of the paste() functionality is built in allowing one to skip writing paste() within cat(). However, the cat() function's only downside is you have to force new lines via \n appended at the end or fill = TRUE (uses default print width).

e.g.

cat("Hello!\n")
Hello!

cat("Hello","World!\n")
Hello World!

cat("Hello","World!\n", sep = "")
HelloWorld!

It is for this very reason why you should use cat() when designing a print.*() S3 method.

message()

The message() function is one step better than even cat()! The reason why is the output is distinct from traditional plain text as it is directed to stderr instead of stdout. E.g. They changed the color from standard black output to red output to catch the users eye.

Message Output

Furthermore, you have the built in paste0() functionality.

message("Hello ","World!") # Note the space after Hello
"Hello World!"

Moreover, message() provides an error state that can be used with tryCatch()

e.g.

 tryCatch(message("hello\n"), message=function(e){cat("goodbye\n")})
 goodbye

warning()

The warning() function is not something to use casually. The warning function is differentiated from the message function primarily by having a line prefixed to it ("Warning message:") and its state is consider to be problematic.

warning output

Misc: Casual use in a function may inadvertently trigger heartbreak while trying to upload the package to CRAN due to the example checks and warnings normally being treated as "errors".

stop()

Last but not least, we have stop(). This takes warnings to the next level by completely killing the task at hand and returning control back to the user. Furthermore, it has the most serious prefix with the term "Error:" being added.

Error Output

coatless
  • 20,011
  • 13
  • 69
  • 84
  • Which is the difference between `message("hello world")` and `cat("hello world", sep="\n")` when designing a print.* S3 method? – antonio Dec 26 '16 at 22:27
  • 1
    @antonio: `message()` will have red text and the other will not. Now, if you add variable to `cat`, e.g. `cat("hello world", "antonio", sep="\n")`, the difference will be one line with `hello world` and another with `antonio`. Though, `message()` will keep everything on the same line. – coatless Dec 26 '16 at 23:41
  • 2
    Thanks for the great response. I was looking for something similar, but my question was labeled "lazy": http://stackoverflow.com/questions/36065232/how-should-i-select-how-to-print-with-r – burger May 17 '17 at 17:25
  • Nice post. Your `print` example shows `print(paste0("Hello","World!"))` giving `[1] "Hello World!"` but it should be `[1] "HelloWorld!"` (without space - as you state in the explanation) – mark Jun 27 '17 at 06:02
  • Another limitation of `print` is that it doesn't expand escape characters. So, `print(paste0('Spam\n', 'Egg\n', 'Spam\n'))` produces `[1] "Spam\nEgg\nSpam\n"` instead of putting each item on a separate line. – Nobody Sep 15 '17 at 12:24
  • Would you recommend using `cat()` or `message()` as part of a `summary.*()` function? – Jeffrey Girard Oct 22 '18 at 16:08
  • @JeffreyGirard `cat()` should be used in `print.summary.*()` – coatless Oct 23 '18 at 22:31
  • 1
    What about `write` https://stackoverflow.com/questions/1109017/how-do-you-print-to-stderr-in-r – sjd Dec 04 '18 at 10:02
  • @sjd `write()` can be used... But, this is uncommon as it primary purpose is writing data to a file. Thus, I've left it out of this list. Instead, I would suggest using `cat(..., file = stderr())` to change from using `stdout()` to `stderr()`. These are part of the [display connections](https://stat.ethz.ch/R-manual/R-devel/library/base/html/showConnections.html). Moreover, they're apart of the more general [connections](https://stat.ethz.ch/R-manual/R-patched/library/base/html/connections.html) framework. – coatless Oct 16 '19 at 16:04
  • So if I want my function to print what it's doing to the user (e.g. "Estimating parameters. Please wait."), should I use `cat` or `message`? I am having a bit of trouble understanding what constitutes "diagnostic" feedback. – Waldir Leoncio Feb 04 '20 at 11:05
  • 1
    @WaldirLeoncio "Diagnostic" output is what is being printed for example "at level info". Unfortunately, that concept does not exist with `message()` or `cat()`. I looked at whether there is a mature logging interface for R as is the case of most systems today (e.g. the [slf4j](http://www.slf4j.org/apidocs/org/slf4j/Logger.html) API for Java), and there **is**: [futile.logger](https://www.r-bloggers.com/better-logging-in-r-aka-futile-logger-1-3-0-released/) - [At CRAN](https://cran.r-project.org/web/packages/futile.logger/index.html). – David Tonhofer Feb 22 '20 at 11:53
  • You completely missed the fact that `print()` is the only one to print certain objects, like `data.frame` ... And then how to print these to stderr? – Tomas Mar 29 '20 at 18:36