6

I'm trying to define the print method for my new object and use the name of the object passed to print using deparse(substitute(y)). This works perfectly using the print function explicitly:

obj <- structure(list(x = 1), 
                 class = "new_obj")

print.new_obj <- function(y){
  cat("New object name:\n")
  print(deparse(substitute(y)))
}

print(obj)
# New object name:
#   [1] "obj"

But when the object is called by name on its own the resulting print function doesn't detect the name:

obj
# New object name:
#   [1] "x"

Is there a standard way to change the behaviour of the implicit call to print when passing an object name on its own?

EDIT: have changed the function argument to y to represent object being passed, to demonstrate that "x" is returned no matter what in the second call.

Andy Baxter
  • 5,833
  • 1
  • 8
  • 22
  • the standard way is the method you have already discovered. `deparse(substitute(x))` – Daniel O Apr 06 '20 at 11:55
  • In both cases it's calling `deparse(substitute(x))` through the `print.new_obj` function, but in the second case it's passing a 'x' pseudo-object or something to the call, and returning "x" no matter the name of the object. Same behaviour when the function arguments are expressed as things other than `x` (still returns "x"). – Andy Baxter Apr 06 '20 at 12:12
  • 1
    Eli Holmes answer suggests this behavior might be unavoidable: https://stackoverflow.com/questions/10520772/in-r-how-to-get-an-objects-name-after-it-is-sent-to-a-function – Greg Apr 06 '20 at 13:29

1 Answers1

2

It is easier to explain what's going on than it is to fix it. If we start by looking at the generic print we can see it simply dispatches the class-appropriate print method via UseMethod("print"):

print
#> function (x, ...) 
#> UseMethod("print")

So when you call print(obj), you are calling the generic function print(obj) first, which then calls print.new_obj(obj). We can confirm this by adding print(sys.calls()) to your print method:

print.new_obj <- function(y){
  print(sys.calls())
  cat("New object name:\n")
  cat(deparse(substitute(y)))
}

print(obj)
#> [[1]]
#> print(obj)
#> 
#> [[2]]
#> print.new_obj(obj)
#> 
#> New object name:
#> obj

So far, so good, and I suspect you already knew all this.

What happens now, when you just type obj into the console?

obj
#> [[1]]
#> (function (x, ...) 
#> UseMethod("print"))(x)
#> 
#> [[2]]
#> print.new_obj(x)
#> 
#> New object name:
#> x

Now we can see where the x comes from. It is taken from a behind-the-scenes call to the generic print which is actually called as an unnamed function. Hence the name of the variable is not actually included in the call stack. There are other questions on SO where it says this makes the problem insoluble. This isn't true; it just means you will need to look outside of the call stack for your object:

print.new_obj <- function(y){
  obj_name <- deparse(substitute(x, parent.frame()))
  if (obj_name != "x")
  {
    obj_name <- names(which(sapply(ls(envir = parent.frame(2)), function(v) 
      identical(y, get(v, envir = parent.frame(2))))))[1]
    cat("New object name:\n", obj_name)
  }
  else cat("New object name:\n", deparse(substitute(y)))
}

print(obj)
#> New object name:
#>  obj
obj
#> New object name:
#>  obj

Of course, you wouldn't want to use this in production code, for all sorts of reasons. It is not particularly useful or logical for a data structure to know what name it has been assigned in a particular environment, and would not be an idiomatic way to write a package for other users.

Still, nice to know it is possible.

Allan Cameron
  • 147,086
  • 7
  • 49
  • 87
  • 1
    Thanks Allan, that's a fabulously helpful insight - handy to know that `print(sys.calls())` can trace what's being done. Wasn't sure how to find out how the print method was being called at all. Handy interesting method for solving, but I think you're right - maybe just avoid having to do this at all. It was as a hint for package users how they might access the data contained (but not printed out). Will look at other ways of pointing to this. – Andy Baxter Apr 07 '20 at 11:31