Suppose I have a function like the below in a large R script, and I am trying to refactor it into a package to make it re-usable in other scripts:
mymean <- function(x) {
assert_that(is.numeric(x))
assert_that(!anyNA(x))
return(mean(x))
}
If I drop this function into my package code unmodified, it will of course not work, unless I also add importFrom(assertthat,assert_that) to my package's NAMESPACE file (either directly or via Roxygen2 comments). In this case, the function is a toy example and the solution is simple. However, for real functions, it may be a significant chore to determine all the required imports as I adopt the function into my package. Is there any way for me to automatically determine all the required imports for a function, and ideally output them as either NAMESPACE import statements or the equivalent Roxygen2 comments so I can copy them right into my package code?
The complication is that variable names are resolved at run time. So calling the same function multiple times may actually use different functions depending on the function environment and what's on the search path at the time. But if you have a function defined, you could extract the unbound variables and then find where they are on the search path and then extract those packages. Here's such a function
get_packages <- function(fun, ignore=c("package:stats", "package:base")) {
stopifnot(is.function(fun))
globals <- codetools::findGlobals(mymean)
pks_raw <- lapply(globals, find, mode="function")
pks <- sapply(pks_raw, function(x) if(length(x)==0) "??" else x[1])
setdiff(unique(unlist(pks)), ignore)
}
Which you can use with
mymean <- function(x) {
assert_that(is.numeric(x))
assert_that(!anyNA(x))
return(mean(x))
}
get_packages(mymean)
# [1] "package:assertthat"
(assuming you have assertthat loaded)
You could also change setdiff(unique(unlist(pks)), ignore) to split(globals, pks) if you want a break down of where the functions are coming from
$`package:assertthat`
[1] "assert_that"
$`package:base`
[1] "!" "{" "anyNA" "is.numeric" "mean" "return"
And if you put in a function that doesn't exist (yet), it will return "??"
mymean <- function(x) {
assert_that(is.numeric(x))
assert_that(!anyNA(x))
oops(x)
return(mean(x))
}
get_packages(mymean)
# [1] "package:assertthat" "??"
And if you plan to copy functions a bunch, you can make it easier on yourself by prefix them with the correct namespace from the get go
mymean <- function(x) {
assertthat::assert_that(is.numeric(x))
assertthat::assert_that(!anyNA(x))
return(mean(x))
}
This is the syntax that's required when you are writing a package. So it just makes it easier to reuse code in general. No guessing required and no need to make sure the package is on the search path.