I am writing a function that logs different things. If the user does not provide an object name, I want to infer it. I found out I can use ensym
to get the name of what the object was binded to.
Since I have several functions I want to use this on, I want to have it in its own function. But when I pass the object to the second function,ensym
does not give what I need anymore.
The following function returns test_data
, but I want it to return starwars
starwars = dplyr::starwars
get_obj_name <- function(obj) {
var = rlang::ensym(obj)
print(as.character(var))
}
test_fun <- function(test_data, hej = NULL) {
if (is.null(hej)) {
get_obj_name(test_data)
}
}
test_fun(starwars)
As shown by Allan, your get_object_name(x)
is essentially just deparse(substitute(x))
, and there is comparatively little benefit to wrapping that into its own function. In fact, Allan’s solution is idiomatic R.
Furthermore, wrapping this into its own function is a lot more complex than just copying the deparse(substitute(x))
part, since substitute
is performing non-standard evaluation in a specific context and is not referentially transparent.
However, I do believe that there’s a benefit to encapsulating the entire expression (including the if (is.null(hej))
check into its own function if you find yourself using this pattern very frequently. And R can do this. So here is how I would suggest rewriting your test_fun
:
test_fun <- function (test_data, hej = NULL) {
hej <- arg_label(test_data, hej)
…
}
Note that I changed the name of your get_obj_name
function to better reflect the fact that it works on argument names (it won’t work on other variables). (I also removed the unnecessary prefix get_
which is implied here, and just adds clutter.)
Here’s the implementation:
arg_label <- function (arg, label, caller = parent.frame()) {
if (is.null(label)) {
deparse(do.call("substitute", list(substitute(arg), caller)))
} else {
label
}
}
The detour via do.call("substitute", …)
ensures that the argument name is retrieved in the context of the caller, instead of the current context.
Adding the optional parameter caller
is unnecessary but ensures that this function can still be used in a deferred context, i.e. where we provide the environment of the caller explicitly. I recommend generally adding such an argument to functions that use non-standard evaluation in the caller’s context.