r-lib/backports

add paste & paste0 from R 4.0.1

Closed this issue · 4 comments

from the release notes

CHANGES IN R 4.0.1
NEW FEATURES

  • paste() and paste0() gain a new optional argument recycle0. When set to true, zero-length arguments are recycled leading to character(0) after the sep-concatenation, i.e., to the empty string "" if collapse is a string and to the zero-length value character(0) when collapse = NULL.
    A package whose code uses this should depend on R (>= 4.0.1).
mllg commented

TBH, I have a hard time understanding the intended behavior after reading both news and man page.

Understandably. There has been a very lengthy discussion about how it should behave r-devel.

As previously, paste0 is just paste currying the sep argument to "":

paste0 <- function(..., collapse = NULL, recycle0 = FALSE)  paste(..., sep = "", collapse = collapse, recycle0 = recycle0)

Only difference is that it now also forwards the recycle0 argument.

paste with recycle0 = FALSE (the default) behaves as previously.

To understand the new behavior for recycle0 = TRUE it helps to think of paste as two different functions by considering the following identity:

paste <==> function(..., sep = " ", collapse = NULL, recycle0 = FALSE) 
   paste(paste(..., sep = sep, recycle0 = recycle0), collapse = collapse)

Only the inner part of paste, the one that concatenates that performs an element wise concatenation, is affected by the new recycle0 argument. Previously and now with recycle0 = FALSE, paste would not recycle zero-length arguments but replace them by rep("", max(sapply(list(...), length))). This means that paste0("Hello", character(0), "!") would (somewhat unexpectedly) yield "Hello !". With the new recycle0 = TRUE, paste recycles zero-length arguments just like e.g. + does for integers:
paste0("Hello", character(0), "!", recycle0 = TRUE) yields character(0), just like 1L+integer(0)==integer(0) or (1:3)+integer(0)==integer(0).

you could implement the new paste like this (and paste0 accordingly):

paste <- function(..., sep = " ", collapse = NULL, recycle0 = FALSE) {
  if(recycle0 && any(vapply(list(...), length, FUN.VALUE = integer(1))==0)) {
    paste(sep = sep, collapse = collapse) # for argument checking
    if (is.null(collapse)) character(0) else "" 
  } else base::paste(..., sep = sep, collapse = collapse)
}

In words: if recycle0 is set to true & there are zero-length arguments paste returns the empty string "" if collapse is a string and to the zero-length value character(0) when collapse = NULL.

See also my SO question for an application.

This would be indeed nice to have.

Great, thank you!