Any way to automatically convert function calls to double colon (::) notation?

71 views Asked by At

I am creating an R package where I want to store several functions I wrote in the past. As I understand that I should use the double colon operator package::function whenever I call an external function, I am going through my code in RStudio to fix all function calls.

Is there a way to script/automatize this? It looks like something that should not be done manually, yet I couldn't find anywhere a way to do this.

PS: I am aware that I can also add @importFrom package function to the NAMESPACE both directly and through #' @importFrom package function, but that involves a lot of manual typing as well, plus a good deal of back and forth from the body of the function to the header.

1

There are 1 answers

0
jay.sf On

Basically, what you want is to find patterns and prefix them with something, where gsub is your friend.

Consider the following as a sketch of an approach!

You might have a named list with names and functions of the external packages like this.

funs_p1 <- c('exfun1a', 'exfun1b')
funs_p2 <- c('exfun2')
funs_p3 <- c('exfun3')
(fun_lst <- list(pac1=funs_p1, pac2=funs_p2, pac3=funs_p3))
# $pac1
# [1] "exfun1a" "exfun1b"
# 
# $pac2
# [1] "exfun2"
# 
# $pac3
# [1] "exfun3"

Then best cd in your working directory,

setwd('fundir')

and list the .R scripts using list.files.

rscripts <- list.files(pattern='.R$')

Then in a loop through the file names of your R scripts using lapply,

  1. readLines into an object
  2. gsub patterns with function names and prefix them with package names and the colons in a for loop
  3. writeLines to the .R scripts (I prefixed them with 'changed_' what should be easily removable but does not overwrite your original files, comment out the line if you are sure, otherwise it returns only the text to be written)

lapply(rscripts, \(x) {
  rl <- readLines(x)
  for (i in seq_along(fun_lst)) {
    rl <- gsub(sprintf('(%s)', paste(paste0(fun_lst[[i]], '\\('), collapse='|')), 
               sprintf('%s::\\1', names(fun_lst)[i]), rl)
  }
  # writeLines(rl, paste0('changed_', x)) |> invisible()
  return(rl)
})
# [[1]]
# [1] "myfun1a <- function() {"         
# [2] "  a <- pac1::exfun1a(some, args)"
# [3] "  mean(a)"                       
# [4] "}"                               
# [5] ""                                
# [6] "myfun1b <- function() {"          
# [7] "  a <- pac3::exfun3(some, args)" 
# [8] "  sum(a)"                        
# [9] "}"                               
# 
# [[2]]
# [1] "myfun2 <- function() {"          
# [2] "  a <- pac1::exfun1b(some, args)"
# [3] "  mean(a)"                       
# [4] "}"                               
# 
# [[3]]
# [1] "myfun3 <- function() {"         
# [2] "  a <- pac2::exfun2(some, args)"
# [3] "  sum(a)"                       
# [4] "}"  

Notice, that this is dangerous and might overwrite your work without undo! Therefore, be sure to create a backup copy and practice with toy examples like this one.

Caveats: There are functions with the same name throughout the packages, e.g. stats::filter and dplyr::filter, you should exclude them to not produce a mess. Also, if objects have the same names or parts, they might get prefixed as well, I tried to approach that by adding opening the parenthesis to the patterns.


Data:

Store these lines in three separate .R scripts to reproduce:

# fun1.R:
myfun1a <- function() {
  a <- exfun1a(some, args)
  mean(a)
}

myfun1b <- function() {
  a <- exfun3(some, args)
  sum(a)
}


# fun2.R:
myfun2 <- function() {
  a <- exfun1b(some, args)
  mean(a)
}


# fun3.R:
myfun3 <- function() {
  a <- exfun2(some, args)
  sum(a)
}