Dapatkan semua fungsi bersumber

11

Di R, saya menggunakan source()untuk memuat beberapa fungsi:

source("functions.R")

Apakah mungkin untuk mendapatkan daftar semua fungsi yang didefinisikan dalam file ini? Sebagai nama fungsi. (Mungkin source()sendiri entah bagaimana dapat mengembalikannya?).

PS: Cara terakhir adalah memanggil source()kedua kalinya seperti local({ source(); })dan kemudian melakukan ls()fungsi-fungsi di dalam dan menyaring, tapi itu terlalu rumit - apakah ada solusi yang lebih mudah dan kurang canggung?

TMS
sumber
1
Ini tidak digunakan source(), tetapi utas lama ini mungkin menarik bagi Anda.
Andrew
1
@Andrew terima kasih, saya sudah memeriksa solusi yang diusulkan tetapi kedengarannya jauh lebih gila daripada pilihan terakhir yang saya sajikan dalam pertanyaan :)
TMS
2
Saya tidak tahu kalau solusi ini lebih gila:envir <- new.env() source("functions.R", local=envir) lsf.str(envir)
LocoGris
2
Buat paket dari file sumber Anda. Maka Anda mendapatkan semua keuntungan termasuk paket namespace.
Roland
@TMS, salah paham pertanyaan Anda / tidak membaca bahwa Anda ingin fungsi yang didefinisikan . Permintaan maaf!
Andrew

Jawaban:

7

Saya pikir cara terbaik adalah dengan sumber file ke lingkungan sementara. Permintaan lingkungan itu untuk semua fungsi, lalu salin nilai-nilai itu ke lingkungan induk.

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")
MrFlick
sumber
terima kasih, solusi ini terlihat menjanjikan, sebagai satu-satunya saat ini! Yang mengejutkan, yang memiliki paling sedikit upvotes. Itu yang saya sebutkan sebagai pilihan terakhir, tetapi menggunakan new.env()alih-alih elegan local({ })yang saya tidak yakin apakah itu akan berhasil dengan assignkerangka orangtua.
TMS
1) apakah menurut Anda itu akan berhasil local()? Dan BTW, 2) apa yang Anda lakukan dalam for loop: apakah tidak ada fungsi untuk menggabungkan lingkungan?
TMS
1
@ TSs Ini mungkin bekerja dengan lokal, meskipun saya belum mencobanya. Saya tidak mengetahui cara lain untuk menyalin semua variabel dari satu lingkungan ke lingkungan lain. Ini bukan operasi yang umum.
MrFlick
Saya pikir attachdapat digunakan untuk, dengan baik, melampirkan satu lingkungan ke yang lain. Meskipun Anda harus menggunakan posargumen daripada menentukan parent.frame. Dan itu hanya akan berfungsi dengan baik untuk menyalin seluruh lingkungan, forloop MrFlick memungkinkan Anda hanya menyalin fungsi.
Gregor Thomas
5

Agak kikuk tetapi Anda bisa melihat perubahan pada objek sebelum dan sesudah sourcepanggilan seperti ini.

    # 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"
Andrew Chisholm
sumber
Terima kasih! Saya punya ide ini juga tetapi itu tidak berhasil karena alasan yang sangat sederhana - jika paket sudah dimuat (yang terjadi setiap saat ketika saya men-debug kode, saya hanya sumber ulang sumber), maka tidak mengembalikan apa-apa.
TMS
3

Saya pikir regex ini menangkap hampir setiap jenis fungsi yang valid (operator biner, fungsi penugasan) dan setiap karakter yang valid dalam nama fungsi, tetapi saya mungkin telah melewatkan case edge.

# 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<-`"
alan ocallaghan
sumber
1
fyi Saya pikir ini bukan solusi yang baik tetapi ini jelas merupakan solusi yang menyenangkan . Saya mungkin akan mengkonversi file ke paket jika saya benar-benar membutuhkan informasi ini.
alan ocallaghan
Saya telah melewatkan dua kasus tepi! Fungsi dapat dimulai dengan .dan fungsi penugasan ( `foo<-`<- function(x, value)ada.
alan ocallaghan
Saya gunakan =untuk penugasan, ini tidak akan menangkap fungsi saya ...
Gregor Thomas
Hasil tangkapan yang bagus diedit. Saya akan perhatikan bahwa R memungkinkan Anda melakukan hal-hal konyol seperti ` d d` <- function(x)yang saat ini tidak tertangkap. Saya tidak ingin regex terlalu konyol, meskipun saya mungkin mengunjungi kembali.
alan ocallaghan
Juga, Anda bisa menetapkan fungsi dengan assign, <<-, dan ->. Dan akan sangat sulit untuk membuat pendekatan ini memperhitungkan fungsi-fungsi yang didefinisikan dalam fungsi, tetapi sebenarnya tidak ada di lingkungan bersumber. Jawaban Anda harus bekerja dengan baik untuk kasus standar, tetapi Anda sebenarnya tidak ingin menulis p parser dari regex.
Gregor Thomas
1

Jika ini adalah skrip Anda sendiri sehingga Anda memiliki kendali atas cara memformatnya, konvensi sederhana akan cukup. Pastikan saja setiap nama fungsi dimulai pada karakter pertama pada barisnya dan bahwa kata tersebut functionjuga muncul pada baris tersebut. Penggunaan kata lain functionakan muncul pada baris yang dimulai dengan spasi atau tab. Maka solusi satu baris adalah:

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

Keuntungan dari pendekatan ini adalah bahwa

  • ini sangat sederhana . Aturan hanya dinyatakan dan hanya ada satu baris sederhana kode R yang diperlukan untuk mengekstrak nama fungsi. Regex juga sederhana dan untuk file yang sudah ada sangat mudah untuk memeriksa - cukup ambil kata functiondan periksa apakah setiap kemunculan yang ditampilkan mengikuti aturan.

  • tidak perlu menjalankan sumbernya. Itu sepenuhnya statis .

  • dalam banyak kasus Anda tidak perlu mengubah file sumber sama sekali dan dalam kasus lain akan ada perubahan minimal. Jika Anda menulis skrip dari awal dengan mengingat hal ini, bahkan lebih mudah untuk mengaturnya.

Ada banyak alternatif lain di sepanjang gagasan konvensi. Anda dapat memiliki regex yang lebih canggih atau Anda dapat menambahkan # FUNCTIONdi akhir baris pertama dari definisi fungsi apa pun jika Anda menulis skrip dari awal dan kemudian mengeluarkan frasa itu dan mengekstrak kata pertama pada baris tersebut tetapi saran utama di sini tampaknya sangat menarik karena kesederhanaannya dan kelebihan lainnya yang tercantum.

Uji

# 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"
G. Grothendieck
sumber
lapply(x, function(y) dostuff(y))akan mematahkan ini
alan ocallaghan
@alan ocallaghan, Contoh Anda melanggar aturan yang dinyatakan sehingga tidak dapat terjadi secara sah. Untuk menulis ini dan tetap berada dalam aturan, seseorang harus memulai fungsi pada baris baru yang di-indentasi atau kita harus membuat indentasi yang baru.
G. Grothendieck
Saya pikir utilitas ini sangat terdegradasi jika Anda memerlukan pemformatan khusus, karena itu mungkin perlu mengubah file - dalam hal ini, Anda mungkin juga menyarankan pengguna membaca nama fungsi secara manual
alan ocallaghan
1
Itu hanya pertimbangan jika Anda tidak mengontrol file tetapi kami telah mengecualikan kemungkinan itu. Menggunakan konvensi sangat umum dalam pemrograman. Saya sering meletakkan # TODOseluruh kode saya sehingga saya dapat memahami apa yang harus saya lakukan, misalnya. Kemungkinan lain di sepanjang baris yang sama adalah menulis # FUNCTIONdi akhir baris pertama dari setiap definisi fungsi.
G. Grothendieck
1
mencoba melakukan penguraian dengan regex adalah jalan menuju neraka ....
TMS
0

Ini mengadaptasi kode yang digunakan dalam posting dari komentar saya untuk mencari urutan token (simbol, operator penugasan, kemudian berfungsi), dan harus mengambil fungsi yang telah ditentukan. Saya tidak yakin apakah itu kuat seperti jawaban MrFlick, tetapi itu adalah pilihan lain:

source2 <- function(file, ...) {
  source(file, ...)
  t_t <- subset(getParseData(parse(file)), terminal == TRUE)
  subset(t_t, token == "SYMBOL" & 
           grepl("ASSIGN", c(tail(token, -1), NA), fixed = TRUE) & 
           c(tail(token, -2), NA, NA) == "FUNCTION")[["text"]]
}
Andrew
sumber