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 toFALSE
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()
andplot()
.A misspelled argument will not raise an error. This makes it easy for typos to go unnoticed.
See also
rlang's check dots functions: rlang::check_dots_empty()
, rlang::check_dots_used()
and rlang::check_dots_unnamed()
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
#>