rlang::exec with package-specification for the function

Viewed 88

I am having trouble getting rlang::exec() to work when I specify the package for a function call.

Simple examples:

rlang::exec("tibble" , a=1:5) # this fails because package 'tibble' is not yet loaded.
# Error in tibble(a = 1:5) : could not find function "tibble"

library(tibble)
rlang::exec("tibble" , a=1:5) # now it works.

rlang::exec("tibble::tibble" , a=1:5) # this fails.
# Error in `tibble::tibble`(a = 1:5) :
#  could not find function "tibble::tibble"

Ultimately, I am looking to include a command like this in an R package that I am making. It would be like this:

foo <- function(stuff , fun , args) {
    # do stuff, and then:

    rlang::exec(fun , !!!args)

    # do more stuff.
}

and I want the user to be able to pass a function from a specific package. Something like this:

foo(stuff = whatever , fun = "pkg::fun" , args = list(a=1,b=2) )

And the user should be able to do that without first loading package pkg, right?

2 Answers

You can get around this by using rlang::call2 to construct your expression before evaluating it with base::eval, rather than using rlang::exec which both constructs and evaluates the expression at the same time. call2 has an argument .ns which allows you to define the namespace in which to look for the function, i.e.:

base::eval(rlang::call2("tibble", a = 1:5, .ns = "tibble"))

or in your last example

base::eval(rlang::call2("foo", !!!args, .ns = "pkg"))

How about a tryCatch?

foo <- function(stuff , fun , args) {
  # do stuff, and then:
  
  tryCatch(rlang::exec(fun, !!!args), 
           error=function(e) rlang::exec(eval(parse(text=fun)), !!!args))
  
  # do more stuff.
}

foo(stuff = whatever , fun = "+" , args = list(a=1, b=5) )
#> [1] 6
foo(stuff = whatever , fun = "tibble::tibble" , args = list(a=1:5))
#> # A tibble: 5 x 1
#>       a
#>   <int>
#> 1     1
#> 2     2
#> 3     3
#> 4     4
#> 5     5

Created on 2021-07-09 by the reprex package (v2.0.0)


Alternatively, you could ask your users to input tibble::tibble instead of "tibble::tibble".

foo <- function(stuff , fun , args) {
  # do stuff, and then:
  
  rlang::exec(fun, !!!args)
  
  # do more stuff.
}

foo(stuff = whatever , fun = "+" , args = list(a=1, b=5) )
#> [1] 6

# this will not work
foo(stuff = whatever , fun = "tibble::tibble" , args = list(a=1:5))
#> Error in `tibble::tibble`(a = 1:5): could not find function "tibble::tibble"

# this does!
foo(stuff = whatever , fun = tibble::tibble , args = list(a=1:5))
#> # A tibble: 5 x 1
#>       a
#>   <int>
#> 1     1
#> 2     2
#> 3     3
#> 4     4
#> 5     5

Created on 2021-07-09 by the reprex package (v2.0.0)

Related