1

I want to create an S3 generic in the global environment from a function. I took a look at R.methodsS3::setGenericS3.default and came up with the following:

create_generic <- function(nm) {
  src <- sprintf("%s <- function(obj, ...) UseMethod(\"%s\")", nm, nm)
  expr <- parse(text = src)
  print(expr)
  eval(expr, env = .GlobalEnv)
}

create_generic("cat")
#> expression(cat <- function(obj, ...) UseMethod("cat"))
cat
#> function (obj, ...) 
#> UseMethod("cat")

This works how I'd like it to. However, I've been trying to make this work using quotation and I'm stuck:

library(rlang)

create_generic2 <- function(nm) {
  expr <- expr(!!sym(nm) <- function(obj, ...) UseMethod(!!nm))
  print(expr)
  eval(expr, env = .GlobalEnv)
}

create_generic2("dog")
#> dog <- function(obj, ...) UseMethod("dog")
dog
#> function(obj, ...) UseMethod(!!nm)

This makes use of tidyeval since that's what I'm familiar with, but I'd like to see what this looks like in base R.

I'm interested in any version of this that works without the string manipulation in create_generic.

alexpghayes
  • 673
  • 5
  • 17

2 Answers2

2

In base R:

create_generic <- function(fun_name) {
  new_fun <- function(obj, ...) UseMethod(fun_name)
  assign(fun_name, new_fun, envir = .GlobalEnv)
}

cat("hi\n")
# hi

create_generic("cat")

cat("hi\n")
# Error in UseMethod(fun_name) : 
#   no applicable method for 'cat' applied to an object of class "character"

cat.character <- base::cat

cat("hi\n")
# hi
Nathan Werth
  • 5,093
  • 18
  • 25
  • Woops. I did this and then just printed the function, never tried it out. The `tidyverse` version above also works. – alexpghayes Dec 27 '17 at 22:11
  • 2
    Note that `create_generic("g"); environment(g)` is not the global environment. – G. Grothendieck Dec 28 '17 at 05:22
  • Right if you don't inline the generic name and rely on the closure env to pass it in with a variable you cannot change the environment to something specific like the global env. – Lionel Henry Dec 28 '17 at 13:37
  • Sanity checking myself here: the _binding_ environment for the generic is the global environment, but the _enclosing_ environment is the execution environment of `create_generic`? So doing something like `environment(g) <- globalenv()` will result in an error when trying to use `g` because `fun_name` won't be in the enclosing environment anymore. – alexpghayes Dec 29 '17 at 01:47
1

You can also use expr_interp() to use unquoting operators within functions:

create_generic <- function(name, env = globalenv()) {
  gen <- expr_interp(function(obj, ...) {
    UseMethod(!!name)
  })

  environment(gen) <- env
  assign(name, gen, envir = env)
}

The prefix is expr_ because it is (internally) generic over expression wrappers, e.g. formulas and functions.

Lionel Henry
  • 6,652
  • 27
  • 33