0

I keep running into this issue with functions in R where, if an argument (to a sub-function) is passed via ... that has a name that is a substring of an argument to the main function, R will use the ... argument for the main function argument, even when the main function argument has a default value:

Here's a simple example:

myfunc <- function(a, b_longer = NULL, ...) {
  print(paste("a =", a))
  if(!is.null(b_longer)) {print(paste("b_longer =", b_longer))}
  if(length(list(...)) > 0) {print("... args detected")}
}

When I run:

> myfunc(a = 5, b_longer = 5)
[1] "a = 5"
[1] "b_longer = 5"
> myfunc(a = 5, c = "hello")
[1] "a = 5"
[1] "... args detected"
> myfunc(a = 5, b = "hello")
[1] "a = 5"
[1] "b_longer = hello"

This works as long as the ... argument's name continues to be a substring of the main function argument's name, and doesn't appear to have anything to do with the underscore:

> myfunc(a = 5, b_ = "hello")
[1] "a = 5"
[1] "b_longer = hello"
> myfunc(a = 5, b_l = "hello")
[1] "a = 5"
[1] "b_longer = hello"
> myfunc(a = 5, b_lg = "hello")
[1] "a = 5"
[1] "... args detected"

This is the same outcome when the default value is NA and not NULL:

myfunc2 <- function(a, b_longer = NA, ...) {
  print(paste("a =", a))
  if(!is.na(b_longer)) {print(paste("b_longer =", b_longer))}
  if(length(list(...)) > 0) {print("... args detected")}
}

When I run:

> myfunc2(a = 5, b_longer = 5)
[1] "a = 5"
[1] "b_longer = 5"
> myfunc2(a = 5, c = "hello")
[1] "a = 5"
[1] "... args detected"
> myfunc2(a = 5, b = "hello")
[1] "a = 5"
[1] "b_longer = hello"
> myfunc2(a = 5, b_ = "hello")
[1] "a = 5"
[1] "b_longer = hello"
> myfunc2(a = 5, b_l = "hello")
[1] "a = 5"
[1] "b_longer = hello"
> myfunc2(a = 5, b_lg = "hello")
[1] "a = 5"
[1] "... args detected"

Anyone have any pointers for why this would be happening? Is this default R behavior? If so, why???

I've reproduced the behavior above, and am trying to figure out if this is simply a general behavior of R that I need work around by carefully naming my function arguments so that they are not superstrings of any of the possible arguments to be passed via ..., or if there's some other way to code things that prevents this sort of mis-variable assignment

  • 2
    Yes it's the R default behavior. R does partial name matching on argument names. If there is a complete substring match on a named argument with no ambiguity, it will assign the value to the named argument. It stems from the old days before autocomplete when it tried to save on user typing at a terminal. You cannot change this behavior. – MrFlick Nov 09 '22 at 17:44
  • 2
    See: [What does r use partial name matching](https://stackoverflow.com/questions/14153904/why-does-r-use-partial-matching). Also related: https://stackoverflow.com/questions/25513535/partial-matching-confusion-when-arguments-passed-through-dots and https://stackoverflow.com/questions/15264994/prevent-partial-argument-matching – MrFlick Nov 09 '22 at 17:44
  • Perhaps comically, base R code uses (in perhaps a few places) truncated argument names, which means that much "code of convenience" (scratch-pad of code, perhaps not completely refined) remains in the full set of base functions. Set `options(warnPartialMatchArgs=TRUE)` and run for a while to see evidence of it in whatever packages you are using (CRAN or otherwise). I wonder if this option should be a default for packages being submitted to CRAN, to start cleaning up some unnecessary truncation (no need for code-golf). – r2evans Nov 09 '22 at 17:56
  • 2
    Any formal arguments that come *after* the dot dot dot must be matched exactly so put the dot dot dot earlier if that is the behavior you want. – – G. Grothendieck Nov 09 '22 at 18:05
  • Ahh, thanks so much! I tried searching for this behavior but didn't have quite the right terms, now I do – mikeblazanin Nov 09 '22 at 19:19

1 Answers1

0

As explained in the comments, this is due to R's partial argument matching. One way to avoid this is to use arg names unlikely to be passed to .... For instance, by prefixing with a dot (tidyverse convention) or making the arguments all caps (used in some base functions, e.g., vapply()).

myfunc <- function(.a, .b_longer = NULL, ...) {
  print(paste("a =", .a))
  if(!is.null(.b_longer)) {print(paste("b_longer =", .b_longer))}
  if(length(list(...)) > 0) {print("... args detected")}
}

myfunc(.a = 5, b = "hello")
# "a = 5"
# "... args detected"
zephryl
  • 14,633
  • 3
  • 11
  • 30