Why is message() a better choice than print() in R for writing a package?
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:
print()
cat()
message()
warning()
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.
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.
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.
Ability to print a message in R console to indicate function is successful?
You can use message
or cat
or print
. If you use message
, someone else has the option of invoking your function wrapped with suppressMessages()
. cat
is going to print to the terminal no matter what. Also, you have to end cat
with a \n
if you want the CRLF.
Messages are printed in red.
message("Function Ran Successfully")
cat("Function Ran Successfully\n")
If you want colored messages, use the crayon
package
cat(crayon::green$bold("Function Ran Successfully\n"))
print
is particularly useful if you want to print out a structure, rather than just a single string.
print(head(iris, 2))
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1 5.1 3.5 1.4 0.2 setosa
2 4.9 3.0 1.4 0.2 setosa
Good coding practices with R: message/cat/print/warning/error?
Your example
In this particular case, the only sensible option is to use stop()
, because this situation is an error, and you want to stop the function from continuing when the error is encountered.
The other options will allow code execution to proceed, returning the last evaluated expression in the function. This is not what you want.
In general
Use print()
when you want to provide output for the end-user. Use cat()
for more control over output in the same situation. This is not suitable for "library code", because it cannot be suppressed easily. However, it is the correct way to perform tasks like emitting data to stdout for processing in a shell pipeline, or writing functions that "pretty print" interesting things.
Use message()
for informational messages that are not warnings or errors, but that arise during some computation where the user didn't specifically request output. For example, if you are developing a package, and you want to inform the user that they can install a special dependency to improve performance, you can use message()
to emit this message. Messages can be suppressed by the user with suppressMessages
.
Use warning()
to indicate problematic situations that do not require function execution to abort. An example would be if the user requested an action that would be very slow, or that might cause numerical stability problems in a later computation. Warnings can be suppressed by the user with suppressWarnings
.
As above, use error()
when something is so totally wrong that the function cannot continue executing. Errors can be caught and handled, (e.g. with tryCatch
), but the user must specifically make this happen.
The technical part
The other functions: message
, warning
, and stop
each signal (or "throw") a condition. I will not go into detail on what conditions are, because Wickham covers them in chapter 8 of Advanced R.
The very simple version is the all 3 of these introduce a signal to the R runtime, that does nothing on its own. The user can then install a "handler" for a specific condition, which is a function that is invoked whenever the condition is signaled.
R has default handlers for the conditions signaled by message()
, warning(),
and error()
. This is how the standard behavior of those functions is implemented.
Capture message, warnings, and errors into web api in R
Following the suggestion for this question: How do I save warnings and errors as output from a function?.
Now I can capture the message, warning, error using tryCatch and store the log type and time in a list.
factory <- function(fun) {
function(...) {
warn <- err <- msg <- NULL
res <- withCallingHandlers(
tryCatch(fun(...), error=function(e) {
new_e <- err
new_e[[length(new_e) + 1]] <- list(time = Sys.time(),
type = "error",
log = conditionMessage(e))
err <<- new_e
NULL
}), warning=function(w) {
new_w <- warn
new_w[[length(new_w) + 1]] <- list(time = Sys.time(),
type = "warning",
log = conditionMessage(w))
warn <<- new_w
invokeRestart("muffleWarning")
}, message = function(m) {
new_w <- msg
new_w[[length(new_w) + 1]] <- list(time = Sys.time(),
type = "message",
log = conditionMessage(m))
msg <<- new_w
invokeRestart("muffleMessage")
})
list(res, warn=warn, err=err, msg=msg)
}
}
test <- function(){
print("AAAAAAA")
message("BBBBBB")
print("CCCC")
warning("DDDDDDDDDDD")
Sys.sleep(1)
warning("EEEEEEEEEEEEEEE")
#stop("FFFFFFFFFFFFF")
warning("GGGGGGGGGGGGGG")
return(NULL)
}
a <- factory(test)()
The results are shown below
> a
[[1]]
NULL
$warn
$warn[[1]]
$warn[[1]]$`time`
[1] "2019-10-21 22:02:39 AEST"
$warn[[1]]$type
[1] "warning"
$warn[[1]]$log
[1] "DDDDDDDDDDD"
$warn[[2]]
$warn[[2]]$`time`
[1] "2019-10-21 22:02:40 AEST"
$warn[[2]]$type
[1] "warning"
$warn[[2]]$log
[1] "EEEEEEEEEEEEEEE"
$warn[[3]]
$warn[[3]]$`time`
[1] "2019-10-21 22:02:40 AEST"
$warn[[3]]$type
[1] "warning"
$warn[[3]]$log
[1] "GGGGGGGGGGGGGG"
$err
NULL
$msg
$msg[[1]]
$msg[[1]]$`time`
[1] "2019-10-21 22:02:39 AEST"
$msg[[1]]$type
[1] "message"
$msg[[1]]$log
[1] "BBBBBB\n"
In the next step, I can use httr function to call RESTAPI to store the logs in the database. Alternatively, the message can be directly stored into database when the messages are generated through updating the factory function.
The only problem is the function cannot capture print
and cat
. However, this question Why is message() a better choice than print() in R for writing a package? suggestion message
function is better than print
and cat
functions. It might be not a big issue for current stage.
R: How to enable printing to console when using `future::plan(sequential)` when in `browser()`?
(Author of future here:) In future (>= 1.20.1) [2020-11-03], the official recommended solution is to use:
plan(sequential, split = TRUE)
This works with browser()
and friends.
Print method in R for function that prints summary even when function output is assigned to an object
per @MrFlick's comment, I changed the function to:
fun_example <- function(xvar, yvar){
# Plots
plot1 = ggplot2::ggplot(data.frame(xvar, yvar), ggplot2::aes(x = xvar, y = yvar)) + ggplot2::geom_line()
plot2 = ggplot2::ggplot(data.frame(xvar, yvar), ggplot2::aes(x = xvar, y = yvar)) + ggplot2::geom_line()
plots <- list(plot1, plot2)
# Correlation
Cor <- cor(xvar, yvar)
message("Visually inspect each plot in the plots object to check for linearity.\n")
if(x$Cor > 0.8){
message("A correlation greater than 0.8 was found.")
} else {
message("A correlation less than or equal to 0.8 was found.")
}
result <- list(plots, Cor)
names(result) <- c("plots", "Cor")
class(result) <- "fun_example"
suppressMessages(return(result))
}
Note that this didn't work for me if the messages did not come before the return() function.
Print string and variable contents on the same line in R
You can use paste
with print
print(paste0("Current working dir: ", wd))
or cat
cat("Current working dir: ", wd)
Related Topics
Understanding Dates and Plotting a Histogram with Ggplot2 in R
How to Count True Values in a Logical Vector
How to Append Rows to an R Data Frame
Use of Lapply .Sd in Data.Table R
Fully Reproducible Parallel Models Using Caret
Select Columns Based on String Match - Dplyr::Select
Merge Multiple Spaces to Single Space; Remove Trailing/Leading Spaces
Issue When Importing Dataset: 'Error in Scan(...): Line 1 Did Not Have 145 Elements'
What Do the %Op% Operators in Mean? for Example "%In%"
Generate Dynamic R Markdown Blocks
How to Convert Data.Frame Column from Factor to Numeric
Linear Regression with a Known Fixed Intercept in R
How to Change 'Maximum Upload Size Exceeded' Restriction in Shiny and Save User File Inputs
Referring to Data.Table Columns by Names Saved in Variables
Removing Display of Row Names from Data Frame
What Are the Double Colons (::) in R
Add (Subtract) Months Without Exceeding the Last Day of the New Month