Search code examples
renvironment-variables

R fn_env with a string argument to get the environment of a function


I would like to use R's fn_env() with a string argument, for example my_fn_env("count"). The users of the program can enter any function, and may also include functions with the :: notation, e.g., "plyr::count", so that this function should return the same as fn_env(plyr::count)

I considered this:

do.call("fn_env", list("plyr::count"), quote=FALSE)

but the quoted string given as an argument remains quoted. Chapter 7 of the e-book "Advanced R" (here) has a function where() which returns the environment when a match is found, accepting strings, but it does not accept the :: notation, apparently.

Any suggestions?


Solution

  • Sure, we can write a function that does that! First, split the input string on ::

    • If there's no split (length = 1), it treats it as a regular function name
    • If there's a split (length = 2), it treats the first part as the package name and the second as the function name

    It then uses get() or getExportedValue() to retrieve the actual function. Finally, it returns the environment of that function.

    This should give you the same results as using fn_env() directly on the function, but with the flexibility of accepting string inputs.

    using<-function(...) {
      libs<-unlist(list(...))
      req<-unlist(lapply(libs,require,character.only=TRUE))
      need<-libs[req==FALSE]
      if(length(need)>0){ 
        install.packages(need)
        lapply(need,require,character.only=TRUE)
      }
    }
    using("rlang")
    
    my_fn_env <- function(fun_name) {
      # Split the string in case it contains namespace specification
      parts <- strsplit(fun_name, "::")[[1]]
      
      if (length(parts) == 1) {
        # Case 1: Regular function name without namespace
        fn <- tryCatch(
          get(fun_name, mode = "function"),
          error = function(e) stop("Function '", fun_name, "' not found")
        )
        return(environment(fn))
      } else if (length(parts) == 2) {
        # Case 2: Function with namespace specification
        pkg <- parts[1]
        fun <- parts[2]
        
        # Check if package is installed and loaded
        if (!requireNamespace(pkg, quietly = TRUE)) {
          stop("Package '", pkg, "' is not installed")
        }
        
        # Get the function from the namespace
        fn <- tryCatch(
          getExportedValue(pkg, fun),
          error = function(e) stop("Function '", fun, "' not found in package '", pkg, "'")
        )
        return(environment(fn))
      } else {
        stop("Invalid function name format")
      }
    }
    
    # normal way using fn_env
    f <- function(x) x + y
    fn_env(f)
    
    # Test our function!
    my_fn_env("f") 
    
    my_fn_env("mean") 
    my_fn_env("plyr::count")