7

I am trying to find methods for specific functions across different packages in R. For example methods(broom::tidy) will return all methods for the function tidy in the package broom. For my current issue it would be better if I could have the methods function in another function like so: f1 <- function(x,y){ methods(x::y) }

(I removed other parts of the code that are not relevant to my issue.) However when I run the function like this:

f1 <- function(x,y){ methods(x::y)}
f1(broom,tidy)

I get the error

Error in loadNamespace(name) : there is no package called ‘x’

If I try to modify it as to only change the function but keep the package the same I get a similar error :

f2 <- function(y){  methods(broom::y)}
f2(tidy)

Error: 'y' is not an exported object from 'namespace:broom'

How can I get the package and function name to evaluate properly in the function? Does this current issue have to do with when r is trying to evaluate/substitute values in the function?

Mike
  • 3,797
  • 1
  • 11
  • 30

1 Answers1

7

Both the :: and methods() functions use non-standard evaluation in order to work. This means you need to be a bit more clever with passing values to the functions in order to get it to work. Here's one method

f1 <- function(x,y){ 
  do.call("methods", list(substitute(x::y)))
}
f1(broom,tidy)

Here we use substitute() to expand and x and y values we pass in into the namespace lookup. That solves the :: part which you can see with

f2 <- function(x,y){ 
  substitute(x::y)
}
f2(broom,tidy)
# broom::tidy

We need the substitute because there could very well be a package x with function y. For this reason, variables are not expanded when using ::. Note that :: is just a wrapper to getExportedValue() should you otherwise need to extract values from namespaces using character values.

But there is one other catch: methods() doesn't evaluate it's parameters, it uses the raw expression to find the methods. This means we don't actually need the value of broom::tidy, we to pass that literal expression. Since we need to evaluate the substitute to get the expression we need, we need to build the call with do.call() in order to evaluate the substitute and pass that expression on to methods()

MrFlick
  • 195,160
  • 17
  • 277
  • 295
  • how do I convert `f2(dplyr, between)` into a function? It returns unevaluated function call. Something along the lines of ` `f2(dplyr, between)(1,2,3)` – slava-kohut Oct 18 '19 at 15:52
  • 1
    @slava-kohut. Well, you could call `\`::\`(dplyr, between)(1,2,3)` directly or you could `eval()` the expression `eval(f2(dplyr, between))(1,2,3)` – MrFlick Oct 18 '19 at 15:55
  • the latter is what I asked. Thanks! – slava-kohut Oct 18 '19 at 15:57
  • @MrFlick random but is there a pure`rlang` solution? I tried to replace `substitute` with `enexpr` or `enquo` but get errors that arg must be a symbol. I originally thought that `substitute` was identical to `enexpr` in `rlang` but I realize now `substitute` might have more features. – Mike Jan 15 '20 at 22:03
  • 1
    @Mike This is the only way I could get it to work for rlang: `f3 <- function(x,y){ x <- rlang::ensym(x); y <- rlang::ensym(y);rlang::eval_tidy(rlang::quo(methods(`::`(!!x, !!y))))}` The parse seems to be a bit quirky around the `::` operator. – MrFlick Jan 16 '20 at 01:41
  • @MrFlick thank you! I think I will stick with `substitute` but I appreciate the `rlang` solution! – Mike Jan 16 '20 at 14:17