52

Is there a "non-Internal" way to get the caller's name, as the function stop does?

The idea is that I have a small function that checks the inputs and halts execution if some condition is not met. This function is called by several others, that use the same validation code. If the input is invalid, the caller's environment is dumped (so I can see arguments passed to the function), and execution halts.

Simplified example:

check <- function(x)
{
    if(x<0)
    {
        print(as.list(parent.frame()))

        evalq(stop("invalid input."), parent.frame())
    }
}

test <- function(x, y)
{
    check(x)
}

I thought that evaluating the expression quote(stop("blah")) in the caller's environment would make it show the caller's name. However, the result is the following:

test(-1, 2)

# $x
# [1] -1
# 
# $y
# [1] 2
# 
# Error in eval(substitute(expr), envir, enclos) : invalid input.

And this doesn't change if I use parent.frame(n) with n>1 in evalq.

So here is the question, actually two questions: 1. Is there a way to get the name of the function that created an environment (assuming it was created as such)? 2. Why the workaround above fails?

EDIT: I said the workaround above fails because I wanted the error message to appear as

Error in test(x, y) : invalid input.

as if the stop statement were a part of test body. So question 2 can be restated as: 2': Why didn't the evaluation of stop("invalid input.") capture the caller's name, considering it was evaluated in the environment of the caller?

Ferdinand.kraft
  • 12,579
  • 10
  • 47
  • 69
  • It's a little odd to do this in a special way for one function. Why not use `traceback()` which will work whenever any error is generated? – hadley Mar 26 '13 at 13:14
  • @hadley, I learned about `traceback` and `sys.call`(`s`) in this post. SO works :-). The ideia is that there are many functions which call `check` to validade their inputs. If `check` detects errors, it dumps the parent environment, so I can see all the inputs passed to the function, and stops. The problem with this is that I can't identify the function that called `check` and caused the error. I could use `options(error=traceback)`, but that dumps the entire stack call, and that may cause some clutter. Thanks for the comments! – Ferdinand.kraft Mar 26 '13 at 16:09
  • if you need a **full reference (function name, line number, source file)**, [follow here](https://stackoverflow.com/q/59537482/684229) – Tomas Dec 31 '19 at 15:06

6 Answers6

39

Thanks @GavinSimpson and @RicardoSporta, but I've figured it out. I'll post an answer in case somebody searches for this in SO.

The name of the function that generated the current call can be retrieved by

deparse(sys.calls()[[sys.nframe()-1]])

This returns a string that contains not only the name of the function, but the entire call object. The name alone can be retrieve by subsetting sys.calls()[[sys.nframe()-1]] before deparsing.

I wanted this because I wrote a function that checks the arguments and halts execution in case of an error. But I wanted this function to (i) dump the environment and (ii) show name of the function one level above in the execution stack. (i) is easy, but I was stuck in (ii).

As for the second question in my post, this is what happens: the expression stop("invalid input") is evaluated in the environment of the test function, but this is not the same thing as if the expression was part of test's body, because the execution stack is different in this 2 scenarios. In the latter case, stop has only test above it, but in the first, it has eval, check and then test upwards. The execution stack, returned by sys.calls() is not the same thing as the enclosing environments. This is what may cause confusion.

demongolem
  • 9,474
  • 36
  • 90
  • 105
Ferdinand.kraft
  • 12,579
  • 10
  • 47
  • 69
25

See ?match.call. For example:

foo <- function() {
  match.call()[[1]]
}

foo()

as.character(foo())

which produces

> foo()
foo
> 
> as.character(foo())
[1] "foo"

A simplified version of your code is

check <- function(x) {
  match.call()[[1]]
}

test <- function(y) {
  check(y)
}

giving

> test(2)
check
> as.character(test(2))
[1] "check"

Note match.call() works via the use of sys.call() (actually it calls sys.call(sys.parent())) when called as I did above with no arguments. So you may wish to consult ?sys.call too.

Gavin Simpson
  • 170,508
  • 25
  • 396
  • 453
  • 1
    Actually I wanted `check` to capture `test`'s name, not the other way round. But following your links, I figured it out: it can be done with `deparse(sys.calls()[[sys.nframe()-1]])`. – Ferdinand.kraft Mar 24 '13 at 06:32
  • @Ferdinand.kraft Ah, sorry, I missed that detail. Glad it helped anyway though. You could of course capture the call at the `test` level and pass it on, but `sys.call()` is neater. – Gavin Simpson Mar 24 '13 at 14:55
  • @GavinSimpson I'd be interested to see how you would accomplish this with sys.call(). Sorry-I hadn't read far enough below. – Atticus29 Dec 13 '16 at 21:35
  • 1
    @GavinSimpson This method fails when called within a namespace (e.g. as in `package::check(1)`). In my version of R, this will return a vector of characters `c("::", "package", "check")`. The preferred call is `deparse(match.call()[[1]])`. – John Haberstroh Nov 06 '17 at 22:47
11

For the record, as Hadley has suggested, you can use sys.call(). For example:

funx = function(...) {
    callingFun = as.list(sys.call(-1))[[1]]
    calledFun = as.list(sys.call())[[1]]
    message(paste(callingFun, " is calling ", calledFun, sep=""))
}

funy = function(...) {funx(...)}

> funy(a = 1, b = 2)
funy is calling funx
qed
  • 22,298
  • 21
  • 125
  • 196
4

Alternative way with the rlang package:

f <- function() {
  name <- rlang::call_frame(n = 2)$fn_name
  rlang::abort(paste("The function", name, "was called and I throw an error."))
}
g <- function() f()
g()
#> Error: The function g was called and I throw an error.

Created on 2019-03-03 by the reprex package (v0.2.1)

Lorenz Walthert
  • 4,414
  • 1
  • 18
  • 24
  • `rlang::call_frame` is now deprecated and replaced by `rlang::trace_back()` (note the underscore in the function name). – MrGumble Apr 20 '22 at 08:42
3

Question #1 is answered by Gavin (use match.call).

However, based on what you are describing, you should also look at traceback(), the output of which you can pass to other functions.


As for Question #2:

It isn't failing, but working exactly as expected. The error you are seeing is not an error in the true sense, but rather the error from your stop(.) function.

If you look at print(evalq), you will see that it in turn calls eval(substitute(expr), envir, enclos)) where expr is your stop("invalid input.")

The correct workaround is to use one more level of quoting

  evalq(quote(stop("invalid input.")))
  # stop("invalid input.")
Ricardo Saporta
  • 54,400
  • 17
  • 144
  • 178
  • Sorry, my second question wasn't very clear. See the edit above. I thought that `evalq(stop("invalid input."), parent.frame())` would give the same result as if the parent function had a `stop("invalid input.")` statement in its body. – Ferdinand.kraft Mar 24 '13 at 06:35
0

To get the function name of the function above, you can simply use:

gsub(pattern="^([A-Za-z0-9]+)(\\({1})(.*)(\\){1})$",replacement="\\1",x=deparse(sys.call(-1)))
RockScience
  • 17,932
  • 26
  • 89
  • 125