Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get all sourced functions

Tags:

r

In R, I am using source() to load some functions:

source("functions.R")

Is it possible to get the list of all functions defined in this file? As function names. (Maybe source() itself can somehow return it?).

PS: The last resort would be to call source() second time like local({ source(); }) and then do ls() inside and filter functions, but that's too complicated - is there easier and less clumsy solution?

like image 476
Tomas Avatar asked Nov 15 '19 13:11

Tomas


4 Answers

I think the best way would be to source the file into a temporary environment. Query that environment for all the functions, then copy those values to the parent environment.

my_source <- function(..., local=NULL) {
  tmp <- new.env(parent=parent.frame())
  source(..., local = tmp)
  funs <- names(tmp)[unlist(eapply(tmp, is.function))]
  for(x in names(tmp)) {
    assign(x, tmp[[x]], envir = parent.frame())
  }
  list(functions=funs)
}

my_source("script.R")
like image 103
MrFlick Avatar answered Nov 07 '22 14:11

MrFlick


It's a bit clunky but you could look at changes in the objects before and after the source call like this.

    # optionally delete all variables
    #rm(list=ls())

    before <- ls()
    cat("f1 <- function(){}\nf2 <- function(){}\n", file = 'define_function.R')
    # defines these
    #f1 <- function() {}
    #f2 <- function() {}
    source('define_function.R')
    after <- ls()

    changed <- setdiff(after, before)
    changed_objects <- mget(changed, inherits = T)
    changed_function <- do.call(rbind, lapply(changed_objects, is.function))
    new_functions <- changed[changed_function]

    new_functions
    # [1] "f1" "f2"
like image 22
Andrew Chisholm Avatar answered Nov 07 '22 13:11

Andrew Chisholm


I think that this regex catches almost every valid type of function (binary operator, assignment functions) and every valid character in a function name, but I may have missed an edge case.

# lines <- readLines("functions.R")

lines <- c(
  "`%in%` <- function",
  "foo <- function",
  "foo2bar <- function",
  "`%in%`<-function",
  "foo<-function",
  ".foo <-function",
  "foo2bar<-function",
  "`foo2bar<-`<-function",
  "`foo3bar<-`=function",
  "`foo4bar<-` = function",
  "` d d` <- function", 
  "lapply(x, function)"
)
grep("^`?%?[.a-zA-Z][._a-zA-Z0-9 ]+%?(<-`)?`?\\s*(<-|=)\\s*function", lines)
#>  [1]  1  2  3  4  5  6  7  8  9 10
funs <- grep("^`?%?[.a-zA-Z][._a-zA-Z0-9 ]+%?(<-`)?`?\\s*(<-|=)\\s*function", lines, value = TRUE)
gsub("^(`?%?[.a-zA-Z][._a-zA-Z0-9 ]+%?(<-`)?`?).*", "\\1", funs)
#>  [1] "`%in%`"      "foo "        "foo2bar "    "`%in%`"      "foo"        
#>  [6] ".foo "       "foo2bar"     "`foo2bar<-`" "`foo3bar<-`" "`foo4bar<-`"
like image 3
alan ocallaghan Avatar answered Nov 07 '22 15:11

alan ocallaghan


If this is your own script so that you have control over how it is formatted a simple convention would be sufficient. Just ensure that each function name starts at the first character on its line and that the word function also appears on that line. Any other use of the word function should appear on a line that starts with a space or tab. Then a one-line solution is:

sub(" .*", "", grep("^\\S.*function", readLines("myscript.R"), value = TRUE))

The advantages of this approach are that

  • it is very simple. The rules are simply stated and there is only one simple line of R code needed to extract the function names. Regex is also simple and for an existing file it is very easy to check -- just grep the word function and check if each occurrence displayed follows the rule.

  • no need to run the source. It is entirely static.

  • in many cases you won't need to change the source file at all and in others there will be minimal changes. If you are writing the script from scratch with this in mind it is even easier to arrange.

There are many other alternatives along the idea of conventions. you could have a more sophisticated regex or you could add # FUNCTION at the end of the first line of any function definition if you are writing the script from scratch and then grep out that phrase and extract the first word on the line but the main suggestion here seems particularly attractive due to its simplicity and the other advantages listed.

Test

# generate test file
cat("f <- function(x) x\nf(23)\n", file = "myscript.R") 

sub(" .*", "", grep("^\\S.*function", readLines("myscript.R"), value = TRUE))
## [1] "f"
like image 1
G. Grothendieck Avatar answered Nov 07 '22 13:11

G. Grothendieck