2012-10-19 33 views
33

Hadley Wickham niedawno zadał ciekawe pytanie na liście r-devel mailing i nie mogąc znaleźć istniejącego pytania na temat StackOverflow, pomyślałem, że może się przydać, ponieważ istnieje również tutaj.Jak programowo utworzyć funkcję R?

Parafrazując:

funkcja R składa się z trzech części: listę argumentów, korpus i środowiskowych. Czy możemy konstruować funkcję programowo z tych trzech elementów?

(Dosyć wyczerpująca odpowiedź znajduje się na końcu wątku w powyższym łączu r-devel, pozostawiam to otwartym dla innych, aby odtworzyć analizę porównawczą różnych rozwiązań i podać je jako odpowiedź, ale pamiętaj, aby zacytować Hadley'a, jeśli to zrobisz. Jeśli nikt nie podejmie decyzji w ciągu kilku godzin, zrobię to sam.)

+0

Myślę, że właśnie dlatego wymyślili seplenienie! – Justin

+0

Dziwne, może to zabrzmieć, widzieliśmy przypadek, w którym można chcieć [parsować ciąg] (http://stackoverflow.com/q/9345373/471093), aby osiągnąć coś podobnego (knitr). – baptiste

Odpowiedz

43

To rozszerzenie dyskusji here.

Nasze trzy części muszą być listą argumentów, ciałem i środowiskiem.

Dla środowiska po prostu użyjemy domyślnie env = parent.frame().

Tak naprawdę nie chcą regularnego starą listę dla argumentów, więc zamiast tego używamy alist który ma trochę inny problem:

”... Wartości nie są oceniane, a oznaczone argumenty bez wartości są dozwolone”

args <- alist(a = 1, b = 2) 

dla ciała, my quote naszej ekspresji, aby uzyskać call:

body <- quote(a + b) 

Jedną z opcji jest konwersja args do pairlist a następnie po prostu wywołać funkcję function użyciu eval:

make_function1 <- function(args, body, env = parent.frame()) { 
     args <- as.pairlist(args) 
     eval(call("function", args, body), env) 
} 

Inną opcją jest stworzenie funkcja pusta, a następnie wypełnić go z pożądanymi wartościami:

make_function2 <- function(args, body, env = parent.frame()) { 
     f <- function() {} 
     formals(f) <- args 
     body(f) <- body 
     environment(f) <- env 

     f 
} 

trzecią opcją jest po prostu użyć as.function:

make_function3 <- function(args, body, env = parent.frame()) { 
     as.function(c(args, body), env) 
} 

I wreszcie, to wydaje się bardzo podobna do pierwszej metody do mnie, z wyjątkiem używamy nieco inny idiom stworzyć wywołanie funkcji, używając substitute zamiast call:

make_function4 <- function(args, body, env = parent.frame()) { 
     subs <- list(args = as.pairlist(args), body = body) 
     eval(substitute(`function`(args, body), subs), env) 
} 


library(microbenchmark) 
microbenchmark(
     make_function1(args, body), 
     make_function2(args, body), 
     make_function3(args, body), 
     make_function4(args, body), 
     function(a = 1, b = 2) a + b 
    ) 

Unit: nanoseconds 
          expr min  lq median  uq max 
1 function(a = 1, b = 2) a + b 187 273.5 309.0 363.0 673 
2 make_function1(args, body) 4123 4729.5 5236.0 5864.0 13449 
3 make_function2(args, body) 50695 52296.0 53423.0 54782.5 147062 
4 make_function3(args, body) 8427 8992.0 9618.5 9957.0 14857 
5 make_function4(args, body) 5339 6089.5 6867.5 7301.5 55137 
+0

joran, wielkie podsumowanie dzięki. Czy chciałbyś wypowiedzieć się na temat benchmarkingu? Wygląda na to, że wynik 1 jest nieznacznie szybszy niż inne funkcje. Ale z głupią alternatywą: "funkcja (a = 1, b = 2, c = 3, d = 4, e = 5, f = 6, g = 7, h = 8, i = 9, j = 10) { exp (a + b) * (c + d)^e/f - ln (g) + h^i^j } 'Dostaję, że funkcja 4 pojawia się znacznie szybciej niż inne. jakieś pomysły? – PatrickT

2

Jest także kwestia programowego tworzenia obiektów alist, ponieważ może to być przydatne do tworzenia funkcji, gdy liczba argumentów jest zmienna.

alist to po prostu nazwana lista pustych symboli. Te puste symbole można utworzyć za pomocą substitute().A więc:

make_alist <- function(args) { 
    res <- replicate(length(args), substitute()) 
    names(res) <- args 
    res 
} 

identical(make_alist(letters[1:2]), alist(a=, b=)) 
## [1] TRUE 
Powiązane problemy