User Rating: 0 / 5

Star InactiveStar InactiveStar InactiveStar InactiveStar Inactive
 

R is a powerful and efficient programming language for statistics, modeling, and data mining. Here I want to provide a quick note on the question "How do I get the name of a function in R?".

Consider the scenario where you write a function myFun in the form of <- function(f) { … } that accepts another function f as parameter and computes something. Maybe your computation takes a lot of time, so you want to output some logging information which contains the name of the function f as well, so the reader can see what is going on. Or maybe you want to generate a report with the results from several functions, so you want the "function names" to be the section titles of the report. Getting the name of a function (or any object) in R is actually quite tricky. Here I discuss a working approach.

One naïve way to do that would be as follows:

myFun <- function(f) {
print(as.character(substitute(f)));
…
}

This works quite well if you call myFun directly, i.e., do:

myFun(sin)
# output: [1] "sin"

However, if you call it indirectly, i.e., do the following, it will fail:

myFun2 <- function(f) myFun(f)
myFun2(sin)
# output: [1] "f"

f is not what we want, we want to print sin. But this is not really possible, as the relationship of names to objects is a one-way relationship. While a variable can point to an object, the object does not have any pointer backwards to the variable. What our naïve approach is doing is basically just to get the exact contents that were supplied in the parameter f as string. If these contents correctly identify a function, say sin, we are good. But if they are just some variables existing inside a calling function, we will get the names of these instead, which is pretty much useless. So our naïve approach won't work unless we call myFun directly – and there is no way to improve this method.

However, we can try something else. In R, basically everything, function, data, anything are objects stored in environments: The job of an environment is to associate, or bind, a set of names to a set of values. We can get all the names of the objects in an environment env using function ls(env) and the object stored under a name name in env via env[name]. We furthermore can check whether one object a is identical to an object b via identical(a, b). So if all objects that exist are stored under names in environments and we can get all names, then we could go through all environments "visible" to the function myFun and compare all objects with the parameter f. If we find one that is identical, we just return its name. OK, that is brute force and slow. But for the above use-cases it may be acceptable.

function.name <- function(f) {
  # first, we try to resolve the name of the function directly
  name <- paste(as.character(substitute(f)), sep="", collapse="");

  # now we check the loaded namespaces if we can find the function
  # we do so backwards,
  namespaces <- loadedNamespaces();
  for(i in seq.int(from=length(namespaces), to=1L, by=(-1L))) {
    nse <- namespaces[i];
    env <- getNamespace(nse);
    for(candidate in ls(env)) {
      if(identical(f, env[candidate])) {
        if(identical(nse, "base")) {
          return(candidate);
        }
        return(paste(nse, candidate, sep="::", collapse=""));
      }
    }
  }

  # now we iterate over all environments in the call stack and try to find the
  # shortest variable that matches the function
  for(i in 0L:(sys.nframe() - 1L)) {
    env <- sys.frame(i);
    notUpdated <- TRUE;

    for(candidate in ls(env)) {
      if(identical(f, env[candidate])) {
        if(notUpdated || (nchar(name) > nchar(candidate))) {
          notUpdated <- FALSE;
          name       <- candidate;
        }
      }
    }
    if(!notUpdated) {
      return(name);
    }
  }

  return(name);
}

Out function function.name above implements this idea. It first checks whether the function passed in is provided by the base package or any other package. If so, we return the name of the function. If not, then it will start at the global environment and work its way down to the environment with the variables of the calling function. As soon as we encounter an environment where a matching function is included, the shortest name for that function in the environment is returned. If the function cannot be found at all, we resort to the naïve approach. This would be the case, for instance, if the function f was not stored in any variable but completely declared directly in the call to function.name.

function.name(sin)
# output: sin(x)
library(methods);
function.name(setClass)
# output: methods::setClass
f <- function(k) sin(k);
ff <- f;
function.name(ff);
# output: f
nest <- function(z) function.name(z);
nest(ff);
# output: f
nest(sin);
# output: sin

For your convenience, this function is implemented in my package utilizeR, which is available here on github. You can install it directly using devtools by doing devtools::install_github("thomasWeise/utilizeR"). If that does not work, first install devtools via install.packages("devtools").

function.name should actually also work with objects that are not functions. The implementation here will fail if invoked from a testthat case, but the one in utilizeR has a work-around for that.