Jesse Kerr
Jesse Kerr

Reputation: 341

Pass named parameters to function that calls function that is not mutate

I am trying to do something very similar to here.

Essentially, I need to pass a named list to a function, and give that named list to another function as its parameters.

If we follow that link, they are able to do it with mutate, and I can replicate that:

df <- tribble(
    ~a,
    1
)

foo <- function(x, args) {
    mutate(x, !!! args)
}

foo(df, quos(b = 2, c = 3)

# A tibble: 1 x 3
      a     b     c
  <dbl> <dbl> <dbl>
1     1     2     3

But if I try to do it with any other function, it fails. Say, if I try to use print, (which the first parameter is x, so I pass a named list with x in it):

print(x= "hello")
[1] "hello"

foo <- function(x, args) {
    print(!!! args)
}

foo(df, quos(x = "hello"))

Error in !args : invalid argument type

I'm not sure why this won't work outside of the "tidyverse" functions. I've tried different combinations of sym, enquo, bang bang, curly curly, etc., but to no avail.

Of course my final goal is not to use print but to use another user-defined-function in its place, so if you have any advice on how to achieve that, I would also greatly appreciate it. (And by the way, I do have to use a named list, I don't think I can use ...).

Thank you so much for your help.

Upvotes: 2

Views: 500

Answers (3)

Lionel Henry
Lionel Henry

Reputation: 6803

You can use rlang::inject():

inject(cbind(!!!letters))

Upvotes: 3

Artem Sokolov
Artem Sokolov

Reputation: 13691

I think it's important to distinguish between literal values (a.k.a. constants) and unevaluated expressions. For example, quos( b=2, c=3 ) will always evaluate to 2 and 3, regardless of context. In such cases, you don't really need those to be quosures or expressions, and a simple list of values will do. You can then use purrr::lift to transform any arbitrary function from taking ... dots to taking a list. No !!! needed:

arglist <- list( replace=TRUE, size=5, x=1:10 )     # Note: list, not quos
sample2 <- purrr::lift(sample)
sample2( arglist )         # Same as sample( x=1:10, size=5, replace=TRUE)
# [1]  7  3 10  8  3

Unevaluated expressions come into play when you want to reference variables or columns that may not have been defined yet. In such cases, you can take advantage of rlang::list2() to capture argument lists spliced by !!!:

subset2 <- function( x, ... )
    rlang::eval_tidy(rlang::expr(subset( {{x}}, !!!rlang::list2(...) )))

# Capture expressions because mpg and cyl are undefined at this point
argexpr <- rlang::exprs( mpg < 15, select=cyl )

# base::subset() doesn't support !!!, but our new function does!
subset( mtcars, !!!argexpr )   # Error in !argexpr : invalid argument type
subset2( mtcars, !!!argexpr )  # Same as subset( mtcars, mpg < 15, select=cyl )
mtcars %>% subset2(!!!argexpr) # Also works with the pipe
#                     cyl
# Duster 360            8
# Cadillac Fleetwood    8
# ...

In the above, subset2() constructs a subset( x, arg1, arg2, etc. ) expression "by hand", then evaluates it. The curly-curly operator is used as a shortcut for !!enquo(x) to paste the user expression directly into the final expression, while rlang::list2() expands and splices all other arguments. By using rlang::list2() instead of base:list() we are adding support for !!! to the function as a whole.

It is also worth highlighting rlang::exec() and rlang::call2(), which are tidyverse equivalents of do.call and call from base. Both offer seamless support of argument splicing with !!!:

rlang::exec( sample, !!!arglist )
eval(rlang::call2( subset, mtcars, !!!argexpr ))

Lastly, @Moody_Mudskipper has a very nice adverbs/tags package. One of those tags adds NSE support to any arbitrary function and has full integration with %>%:

library(tags)    ## installed with devtools::install_github("moodymudskipper/tags")
using_bang$sample( !!!arglist )
using_bang$subset( mtcars, !!!argexpr )
mtcars %>% using_bang$subset( !!!argexpr )

Upvotes: 1

user12728748
user12728748

Reputation: 8506

You could use match.call() inside your function to get the list of arguments and their names:

myfun <- function(x, ...){
    args <- as.list(match.call())[-1]
    print(setNames(unlist(args), names(args)))
    lapply(match.call()[-1], class)
}

myfun(x=list(a=1, b="hi", c="a"), b=5)
#> $x
#> list(a = 1, b = "hi", c = "a")
#> 
#> $b
#> [1] 5
#> $x
#> [1] "call"
#> 
#> $b
#> [1] "numeric"

Upvotes: 0

Related Questions