wch
wch

Reputation: 4117

Using substitute() to get argument names, multiple levels up

Consider this function a(), which prints out the argument that was passed in:

a <- function(x) {
  message("The input is ", deparse(substitute(x)))
}

a("foo")
# The input is "foo"

tmplist <- list(x1 = 1, x2=2)
a(tmplist)
# The input is tmplist

That works. But when a() is called from another function, it no longer prints out the original argument names:

b <- function(y) {
  a(y)
}

b("foo")
# The input is y

b(tmplist)
# The input is y

One solution that seems to work is to wrap in another substitute and an eval:

a1 <- function(x) {
  message("The input is ", deparse(eval(substitute(substitute(x)), parent.frame())))
}

a1("foo")
# The input is "foo"

tmplist <- list(x1 = 1, x2=2)
a1(tmplist)
# The input is tmplist

b1 <- function(y) {
  a1(y)
}

b1("foo")
# The input is "foo"

b1(tmplist)
# The input is tmplist

But this seems inelegant. And it fails if I add another layer:

c1 <- function(z) {
  b1(z)
}
c1("foo")
# The input is z

Is there a good, general way to get the original argument?

Upvotes: 6

Views: 924

Answers (4)

wch
wch

Reputation: 4117

Adapting kohske's answer, here is something that works, but doesn't stop going up the frame stack prematurely, if the variable has the same name in two successive frames. I don't know if it work correctly in all situations, but it seems to handle my needs. The quoting of strings vs. variables is a little different from before, but that's OK for my case.

a <- function(x) {
  newname <- substitute(x)

  # Travel up the frame stack until we hit the top.
  for(i in seq_len(sys.nframe())) {
    oldname <- do.call("substitute", list(as.name(newname), parent.frame(i)))
    newname <- oldname
  }
  message("The input is ", deparse(newname))
}

b <- function(y)  a(y)

c <- function(z)  b(z)

a("adsf")
# The input is adsf
a(foo)
# The input is foo

b("adsf")
# The input is adsf
b(foo)
# The input is foo

c("adsf")
# The input is adsf
c(foo)
# The input is foo

Upvotes: 3

shhhhimhuntingrabbits
shhhhimhuntingrabbits

Reputation: 7475

How about recursively calling your function when

deparse(substitute(x))!=deparse(eval(substitute(substitute(x)), parent.frame())

Upvotes: 0

Aaron - mostly inactive
Aaron - mostly inactive

Reputation: 37764

While this is an interesting question in its own right, I wonder if the best solution is just to pass the variable name as a character, ie, in quotes. Then none of this is necessary. If the object corresponding to the name is needed, it can then be gotten with get or as.name and do.call, depending on how you're using it within the function.

> f0 <- function(x) {message("The input is ", x)}
> f1 <- function(.f1) f0(.f1)
> f2 <- function(.f2) f1(.f2)
> f2("aa")
The input is aa
> f1("bb")
The input is bb
> f0("cc")
The input is cc

Upvotes: 1

kohske
kohske

Reputation: 66852

I'm not sure it this will work well in all situations, but try this:

f0 <- function(x) {
  nn <- substitute(x)
  i <- 1
  while(TRUE) {
    on <- do.call("substitute", list(as.name(nn), parent.frame(i)))
    if (on == nn) break;
    nn <- on
    i <- i + 1
  }
  message("The input is ", nn)
}

f1 <-function(.f1) f0(.f1)
f2 <- function(.f2) f1(.f2)

and then,

> f2(foo)
The input is foo
> f1(poo)
The input is poo
> f0(moo)
The input is moo
> f2(";(")
The input is ;(
> f1(":)")
The input is :)
> f0(":p")
The input is :p

Upvotes: 3

Related Questions