I want to create a function myfun
that can only be used inside another function, in my case dplyr
s mutate
or summarise
. I further do not want to rely on dplyr
s internals (for example mask$...
).
I came up with a quick and dirty workaround: A function search_calling_fn
that checks all function names in the call stack and looks for a specific pattern in the calling functions.
search_calling_fn <- function(pattern) {
call_st <- lapply(sys.calls(), `[[`, 1)
res <- any(unlist(lapply(call_st, function(x) grepl(pattern, x, perl = TRUE))))
if (!res) {
stop("`myfun()` must only be used inside dplyr::mutate or dplyr::summarise")
} else {
return()
}
}
This works as expected as the two examples below show (dplyr
= 1.0.0)
library(dplyr)
myfun <- function() {
search_calling_fn("^mutate|^summarise")
NULL
}
# throws as expected no error
mtcars %>%
mutate(myfun())
myfun2 <- function() {
search_calling_fn("^select")
NULL
}
# throws as expected an error
mtcars %>%
mutate(myfun2())
This approach has one loophole: myfun
could be called from a function with a similar name which is not a dplyr
function. I wonder how I can check from which namespace a function on my call stack is coming. rlang
has a function call_ns
but this will only work, if the function is explicitly called with package::...
. Further, when using mutate
there is mutate_cols
an internal function and mutate.data.frame
an S3 method on the call stack - both seem to make getting the namespace even more complicated.
On a second thought I wonder whether there is a better, more official approach to achieve the same outcome: only allow myfun
to be called within dplyr
s mutate
or summarise
.
The approach should work no matter how the function is called:
mutate
dplyr::mutate
Additional note
After discussing @r2evans answer, I realize that a solution should pass the following test:
library(dplyr)
myfun <- function() {
search_calling_fn("^mutate|^summarise")
NULL
}
# an example for a function masking dplyr's mutate
mutate <- function(df, x) {
NULL
}
# should throw an error but doesn't
mtcars %>%
mutate(myfun())
So the checking function should not only look at the callstack, but also try to see which package a function on the callstack is coming from. Interestingly, RStudios debugger shows the namespace for each function on the callstack, even for internal functions. I wonder how it does this, since environment(fun))
is only working on exported functions.