Skip to content

Extracting specific arguments from "..."

7 messages · Bert Gunter, Iris Simmons, Hadley Wickham +1 more

#
Consider:

f1 <- function(...){
  one <- list(...)[['a']]
  two <- ...elt(match('a', ...names()))
  c(one, two)
}
## Here "..." is an argument list with "a" somewhere in it, but in an
unknown position.
[1] 2 2

Which is better for extracting a specific named argument, one<- or
two<- ?  Or a third alternative that is better than both?
Comments and critiques welcome.

Cheers,
Bert
#
I would use two because it does not force the evaluation of the other
arguments in the ... list.
On Sun, Jan 5, 2025, 13:00 Bert Gunter <bgunter.4567 at gmail.com> wrote:

            

  
  
#
Thanks, Iris.
That is what I suspected, but it wasn't clear to me from the docs.

Best,
Bert
On Sun, Jan 5, 2025 at 10:16?AM Iris Simmons <ikwsimmo at gmail.com> wrote:
3 days later
#
I'd propose an alternative that I think is superior: rely on the semantics
of ... to do the work for you:

f1 <- function(...){
  one <- list(...)[['a']]
  two <- ...elt(match('a', ...names()))
  c(one, two, three(...))
}

three <- function(a, ...) {
  a
}

f1(a = 1, b = 2, c = 3)
#> [1] 1 1 1
On Sun, Jan 5, 2025 at 12:00?PM Bert Gunter <bgunter.4567 at gmail.com> wrote:

            

  
    
#
That's very nice, Hadley. Simple and clean. Never would have thought of it
myself.

As usual, however, in the course of my churnings, I have a further
complication to add. But first ...

**TO ALL**: Feel free to ignore the following, as I'm just fooling around
here and don't want to waste your time with my stupid stuff.

Anyway, the complication is motivated by the use of formals() or otherwise
that *programmatically* generates a character representation of the
arguments I want to select. So, for example:
## Then:
f1 <- function(...){
    ...elt(match(z, ...names())) ## since z gets evaluated in the call
}
## still works.
[1] 1

But I haven't figured out how to modify your suggestion -- at least in a
simple way -- to do the same. Likely I've missed something, though.


Cheers,
Bert
On Wed, Jan 8, 2025 at 12:51?PM Hadley Wickham <h.wickham at gmail.com> wrote:

            

  
  
#
I might add that there seems to be a subtle difference between using
`...elt()` and `match.call()`, which is that the former causes `a` itself
to be evaluated while the latter doesn't:
```
# Some approaches that have been suggested:

# 1. Using `list()` (Bert Gunter)
f1 <- function(...) list(...)[["a"]]
# 2. Using `...elt()` (Bert Gunter)
f2 <- function(...) ...elt(match("a", ...names()))
# 3. Using argument matching (Hadley Wickham)
f3 <- function(...) (\(a, ...) a)(...)
# 4. Using `match.call()`
f4 <- function(...) eval(match.call()[["a"]], parent.frame())

ff <- list(f1 = f1, f2 = f2, f3 = f3, f4 = f4)

sapply(ff, \(f) {
  f(b = 2, a = 1, c = 3)
})
#> f1 f2 f3 f4
#>  1  1  1  1

# View the (defused) arguments after `a` has been accessed:

# returns an expression if the argument has not been evaluated, and a
number if it has
check_forced_args <- function(f) {
  body(f) <- call("{", body(f), quote(rlang::enexprs(...)))
  # pass `f()` some expressions to see which are evaluated
  f(a = cos(0), b = sqrt(4))
}
# make a data frame showing the defused arguments for each function
lapply(ff, check_forced_args) |> do.call(rbind, args = _) |> as.data.frame()

#>         a       b
#> f1      1       2    # all the arguments are forced
#> f2      1 sqrt(4)    # only `a` is forced
#> f3      1 sqrt(4)    # only `a` is forced
#> f4 cos(0) sqrt(4)    # none of the arguments are forced
```

Also, here's a possible way to adapt Hadley Wickham's approach so that it
takes the name of the argument as a string, though it does lose the
elegance:
```
pick_arg <- function(nm) {
  as.function(c(
    setNames(alist(. = , . = ), c(nm, "...")),
    as.symbol(nm)
  ))
}

z <- "a"
f5 <- function(...) {
  pick_arg(z)(...)
}
f5(b = 2, a = 1, c = 3)
#> [1] 1
```

Regards,
Ian
____
Ian Farm, Laboratory Manager
University of Maine Agroecology Lab
On Wed, Jan 8, 2025 at 5:58?PM Bert Gunter <bgunter.4567 at gmail.com> wrote:

            

  
  
#
Thanks Ian (and others),

Taking the advice of ?"..." (which I should have done at the outset ...
duhh!),  the following seems to be the simplest solution, adequate for my
needs at least:

f <- function(x, ...){
   lapply(seq_along(x), \(i)switch(x[i], ..., NA))
}

(the usual argument checking etc. should be added, of course.)

Comparing this to the ...xx() functions that I previously gave, which are
the same as names(list(...)) etc. without the overhead of unnecessary
evaluation, gives:

g <- function(...){
   one <- f(z,...)
   two <- lapply(charmatch(z, ...names()), \(i)...elt(i))
  list(one = one, two = two)
}
$one
$one[[1]]
[1] 1

$one[[2]]
[1] 5


$two
$two[[1]]
[1] 1

$two[[2]]
[1] 5

As always, corrections and comments welcomed.

Cheers,
Bert
On Thu, Jan 9, 2025 at 5:12?AM Ian Farm <ian.farm at maine.edu> wrote: