Skip to contents

[Experimental]

Ensures that dots ... are either empty (if .empty_ok = TRUE), or all named elements in dots are a valid subset of .fn's parameter names. In case of an invalid or .forbidden argument, an informative message is shown and the defined .action is taken.

Usage

check_dots_named(
  ...,
  .fn,
  .additional = NULL,
  .forbidden = NULL,
  .empty_ok = TRUE,
  .action = c("abort", "warn", "inform")
)

Arguments

...

Dots argument to check.

.fn

Function the ... will be passed on to.

.additional

Parameter names within ... that should be treated as valid in addition to .fn's actual parameter names. A character vector.

.forbidden

Parameter names within ... that should be treated as invalid. This has precedence over .additional. A character vector.

.empty_ok

Set to TRUE if empty ... should be allowed, or to FALSE otherwise.

.action

Action to take when the check fails. One of "abort", "warn" or "inform"

Details

check_dots_named() is intended to combat the second one of the two major downsides that using ... usually brings. In chapter 6.6 of the book Advanced R it is phrased as follows:

Using ... comes with two downsides:

  • When you use it to pass arguments to another function, you have to carefully explain to the user where those arguments go. This makes it hard to understand what you can do with functions like lapply() and plot().

  • A misspelled argument will not raise an error. This makes it easy for typos to go unnoticed.

Examples

# We can use `check_dots_named()` to address this second downside:
sum_safe <- function(...,
                     na.rm = FALSE) {
  pal::check_dots_named(...,
                        .fn = sum)
  sum(...,
      na.rm = na.rm)
}

# Note how the misspelled `na_rm` (instead of `na.rm`) silently gets ignored in the original
# function,
sum(1, 2, NA, na_rm = TRUE)
#> [1] NA

# whereas our safe version properly errors:
try(
  sum_safe(1, 2, NA, na_rm = TRUE)
)
#> Error in map(.x, .f, ..., .progress = .progress) : 
#>    In index: 1.
#> Caused by error in `.f()`:
#> ! Invalid named argument provided in `...`: `na_rm`
#>  Valid named arguments for `sum()` include: `na.rm`
#> → Did you mean `na.rm`?

# We can even build an `sapply()` function that fails "intelligently":
sapply_safe <- function(X,
                        FUN,
                        ...,
                        simplify = TRUE,
                        USE.NAMES = TRUE) {
  pal::check_dots_named(...,
                        .fn = FUN)
  sapply(X = X,
         FUN = FUN,
         ...,
         simplify = TRUE,
         USE.NAMES = TRUE)
}

# While the original `sapply()` silently consumes misspelled named arguments via `...`,
sapply(1:5, paste, "hour workdays", sep = "-", colaspe = " ")
#> [1] "1-hour workdays- " "2-hour workdays- " "3-hour workdays- " "4-hour workdays- " "5-hour workdays- "

# `sapply_safe()` will throw an informative error message:
try(
  sapply_safe(1:5, paste, "hour workdays", sep = "-", colaspe = " ")
)
#> Error in map(.x, .f, ..., .progress = .progress) : 
#>    In index: 2.
#> Caused by error in `.f()`:
#> ! Invalid named argument provided in `...`: `colaspe`
#>  Valid named arguments for `paste()` include: `sep`, `collapse`, and `recycle0`
#> → Did you mean `collapse`?

# But be aware that `check_dots_named()` might be a bit rash,
try(
  sum_safe(a = 1, b = 2)
)
#> Error in map(.x, .f, ..., .progress = .progress) : 
#>    In index: 1.
#> Caused by error in `.f()`:
#> ! Invalid named argument provided in `...`: `a`
#>  Valid named arguments for `sum()` include: `na.rm`

# while the original function actually has nothing to complain:
sum(a = 1, b = 2)
#> [1] 3

# Furthermore, it doesn't play nicely with generics that don't expose all of the argument names
# of the method that is eventually invoked (`to` and `by` in the case of `seq()` -> `seq.int()`):
try(
  sapply_safe(X = c(0,50),
              FUN = seq,
              to = 100,
              by = 5)
)
#> Error in map(.x, .f, ..., .progress = .progress) : 
#>    In index: 1.
#> Caused by error in `.f()`:
#> ! Invalid named argument provided in `...`: `to`
#>  Only unnamed arguments are valid for `seq()`.

# To work around this, directly supply the proper method (`seq.int`),
sapply_safe(X = c(0,50),
            FUN = seq.int,
            to = 100,
            by = 5)
#> [[1]]
#>  [1]   0   5  10  15  20  25  30  35  40  45  50  55  60  65  70  75  80  85  90  95 100
#> 
#> [[2]]
#>  [1]  50  55  60  65  70  75  80  85  90  95 100
#> 

# or just provide `to` and `by` *unnamed*:
sapply_safe(X = c(0,50),
            FUN = seq,
            100,
            5)
#> [[1]]
#>  [1]   0   5  10  15  20  25  30  35  40  45  50  55  60  65  70  75  80  85  90  95 100
#> 
#> [[2]]
#>  [1]  50  55  60  65  70  75  80  85  90  95 100
#>