Search code examples
rrlangtidyeval

Wrapper around deparse() and substitute()


I'm trying to write a function that would simply wrap around deparse() and substitute() like so:

get_name <- function(x) {
  deparse(substitute(x))
}

The point of get_name() is to return the character object of a function argument. get_name() is working when called in my global environment like this:

# no problem here, returns "Variable1" as expected
get_name(x = Variable1)
[1] "Variable1"

The problem is when I try and use get_name() in another function. Like the one below:

# a new function that uses get_name()
new_function <- function(arg1) {
  get_name(x = arg1)
}

# returns "arg1"
# should be returning "Variable1"
new_function(arg1 = Variable1)
[1] "arg1"

My initial thinking is that there must be a way around this using environments. However, when trying to use eval() it isn't working because Variable1 isn't actually an object in my global environment, it's just the value in my function argument.

I've also tried other rlang functions like the combination of as_string() and ensym(), and unfortunately that didn't work.


Solution

  • Remember that each function call creates a new frame to store its variables in. Your get_name function has a different frame than new_function. You want the substitution to take place in the frame of new_function and furthermore substitute quotes its first argument when you just call it.

    Here is a function that I think does what you want:

    get_name <- function(x) {
      nm <- substitute(x)
      pf <- parent.frame()
      deparse(do.call(substitute, list(nm,pf)))
    }
    

    We first use substitute to get the argument that you want the name of (arg1 in your example) and store it in nm. We do not need to deparse, because we want a language object.

    Then we use parent.frame to grab the frame of the function that called this one.

    Then use do.call to construct the call to substitute with the name we already grabbed and the parent frame, then deparse that result.

    With your example new_function:

    > get_name <- function(x) {
    +   nm <- substitute(x)
    +   pf <- parent.frame()
    +   deparse(do.call(substitute, list(nm,pf)))
    + }
    > new_function <- function(arg1) {
    +   get_name(x = arg1)
    + }
    > new_function(arg1 = Variable1)
    [1] "Variable1"
    > new_function(arg1 = something_else)
    [1] "something_else"
    

    Then you can do something like this as well:

    myplot <- function(x, y) {
      xl <- get_name(x)
      yl <- get_name(y)
      plot(x, y, 
           xlab=paste('x:', xl),
           ylab=paste('y:', yl))
    }
    myplot(runif(100), rnorm(100))
    

    And the plot has the labels based on get_name.