We need to emit:
What do these messages tend to look like, in the wild, using base tools?
base::message()
and base::stop()
First, let’s review the signatures of message()
and stop()
.
Both make make a message from the loose parts that come via ...
, which is documented as:
zero or more objects which can be coerced to character (and which are pasted together with no separator)
Here’s a tour of message()
calls sprinkled around tidyverse / r-lib, with me pointing out small, recurring challenges. I’ll often have to make some fake objects, so we can actually run the code.
This is about characterizing the problem space as we find it and foreshadowing some existing solutions we already use and some improvement proposals I want to make.
The alternation between literal strings and strings stored as character objects causes a lot syntactical noise. This is why many of us embrace the interpolation offered by the glue package, which I’ll use as a running comparator.
#> Caching nycflights db at path/to/a/thing
#> Caching nycflights db at path/to/a/thing
dim_f <- c(4, 7)
units <- "inch"
message("Saving ", dim_f[1], " x ", dim_f[2], " ", units, " image")
#> Saving 4 x 7 inch image
#> Saving 4 x 7 inch image
sprintf()
offers some hope of relief, because at least the single format string eliminates lots of quotes.
name <- "thing"
replacement <- " by doohickey"
version <- "1.0.0"
message(sprintf("%s is deprecated%s. Deprecated in version '%s'.",
name, replacement, version))
#> thing is deprecated by doohickey. Deprecated in version '1.0.0'.
# glue-ified
message(glue(
"{name} is deprecated{replacement}. Deprecated in version '{version}'."
))
#> thing is deprecated by doohickey. Deprecated in version '1.0.0'.
I’d still rather write, read, and maintain the glue version. You don’t have to bounce between the format string and the arguments supplying values, doing tedious counting or substitution in your head.
Conclusion: string interpolation is critical for any function that makes messages.
Often you want to surround an interpolated stored string with an actual quoting symbol, so it’s clearly distinguished as, e.g., a variable or package name or a field. This invites a very specific set of mistakes: not enough backslashes, backslashes in the wrong place, and unbalanced quotes.
FMT <- "FORMAT"
message("Using: \"", FMT, "\"")
#> Using: "FORMAT"
element <- "ggplot2.thingy"
message("Theme element `", element, "` missing")
#> Theme element `ggplot2.thingy` missing
#> Using: "FORMAT"
#> Using: "FORMAT"
#> Theme element `ggplot2.thingy` missing
Conclusion: Messages are not just “flat” strings. You really need inline styles to convey all the necessary information.
Spoiler: this is the motivation for the cli package’s semantic CLI functionality, which has roots in the ui_*()
functions in usethis.
It’s pretty common that your message incorporates a collection of items Here you see a lot of paste()
and friends, used with the collapse
argument.
x <- structure(1, class = c("alfa", "bravo", "charlie"))
message("Don't know how to automatically pick scale for object of type ",
paste(class(x), collapse = "/"), ". Defaulting to continuous.")
#> Don't know how to automatically pick scale for object of type alfa/bravo/charlie. Defaulting to continuous.
# glue-ified
message(glue("
Don't know how to automatically pick scale for object of type \\
{glue_collapse(class(x), sep = '/')}. Defaulting to continuous."
))
#> Don't know how to automatically pick scale for object of type alfa/bravo/charlie. Defaulting to continuous.
#> `geom_smooth()` using 'this' and 'that'
#> `geom_smooth()` using 'this' and 'that'
Conclusion: Often we want to inject an array of strings and collapse it with some attention to aesthetics, conventions, and grammar.
The next example really starts to show multiple ergonomic problems interacting with each other, i.e. working over a list of interpolated strings and dealing with line breaks.
orig_name <- c(a = "o1", b = "o2")
name <- c(a = "n1", b = "n2")
new_names <- c("b", "a")
message(
"New names:\n",
paste0(orig_name[new_names], " -> ", name[new_names], collapse = "\n")
)
#> New names:
#> o2 -> n2
#> o1 -> n1
# glue-ified
nms <- glue("{orig_name[new_names]} -> {name[new_names]}\n")
nms <- glue_collapse(nms, sep = "\n")
message(glue("New names:\n{nms}"))
#> New names:
#> o2 -> n2
#> o1 -> n1
We’re also starting to see some challenges with glue, where you need to make several calls in sequence, to build up the message. I’ve decided the explicit management of newlines (\n
) is a strong indicator that there’s some missing bit of tooling.
Let’s go out with an extravaganza of interpolation, inline styling and collapsing!
trained <- c(alfa = "apple", bravo = "banana")
message("Multiple formats matched: ",
paste("\"", names(trained), "\"(", trained, ")", sep = "",
collapse = ", "))
#> Multiple formats matched: "alfa"(apple), "bravo"(banana)
# glue-ified
x <- glue('"{names(trained)}"({trained})')
y <- glue_collapse(x, sep = ", ")
message(glue("Multiple formats matched: {y}"))
#> Multiple formats matched: "alfa"(apple), "bravo"(banana)