Come creare una funzione R a livello di codice?

Hadley Wickham ha recentemente fatto una domanda interessante sulla mailing list di r-devel , e non essendo riuscito a trovare una domanda esistente sull’argomento su StackOverflow, ho pensato che potrebbe essere utile esistere anche qui.

Per parafrasare:

Una funzione R consiste di tre elementi: un elenco di argomenti, un corpo e un ambiente. Possiamo build una funzione a livello di programmazione da questi tre elementi?

(Una risposta abbastanza esaustiva è stata raggiunta alla fine del thread nel link di r-devel qui sopra, lascerò questo aperto agli altri per ricreare il benchmarking delle varie soluzioni e fornirglielo come risposta, ma assicurati di citare Hadley se lo fai. Se nessuno salirà in poche ore lo farò da solo.)

Questa è un’espansione sulla discussione qui .

I nostri tre pezzi devono essere una lista di argomenti, un corpo e un ambiente.

Per l’ambiente, utilizzeremo semplicemente env = parent.frame() per impostazione predefinita.

Non vogliamo una normale lista per gli argomenti, quindi usiamo alist che ha un comportamento diverso:

“… i valori non vengono valutati e sono consentiti argomenti con tag senza valore”

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

Per il corpo, quote nostra espressione per ottenere una call :

 body <- quote(a + b) 

Un'opzione è convertire gli args in un pairlist e quindi chiamare semplicemente la funzione function usando eval :

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

Un'altra opzione è creare una funzione vuota e quindi riempirla con i valori desiderati:

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

Una terza opzione è semplicemente usare as.function :

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

E infine, questo mi sembra molto simile al primo metodo, tranne che stiamo usando un linguaggio un po 'diverso per creare la chiamata di funzione, usando il substitute piuttosto che la 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 

C’è anche il problema di creare oggetti alist programmatico poiché può essere utile per creare funzioni quando il numero di argomenti è variabile.

Un alist è semplicemente una lista nominata di simboli vuoti. Questi simboli vuoti possono essere creati con substitute() . Così:

 make_alist <- function(args) { res <- replicate(length(args), substitute()) names(res) <- args res } identical(make_alist(letters[1:2]), alist(a=, b=)) ## [1] TRUE