¿Cómo crear una función R programáticamente?

Hadley Wickham hizo recientemente una pregunta interesante en la lista de correo de desarrollo y, al no poder encontrar una pregunta existente sobre el tema en StackOverflow, pensé que podría ser útil porque también existe aquí.

Parafrasear:

Una función R consta de tres elementos: una lista de argumentos, un cuerpo y un entorno. ¿Podemos construir una función programáticamente a partir de estos tres elementos?

(Se llega a una respuesta bastante completa al final del hilo en el enlace r-devel anterior. Dejaré esto abierto para que otros puedan recrear la evaluación comparativa de las distintas soluciones y proporcionarla como respuesta, pero asegúrese de citar a Hadley si lo haces. Si nadie sube en unas pocas horas, lo haré yo mismo.)

Esta es una expansión de la discusión aquí .

Nuestras tres piezas deben ser una lista de argumentos, un cuerpo y un entorno.

Para el medio ambiente, simplemente usaremos env = parent.frame() de manera predeterminada.

Realmente no queremos una lista antigua regular para los argumentos, así que en su lugar usamos una alist que tiene un comportamiento diferente:

“… los valores no se evalúan y los argumentos etiquetados sin valor están permitidos”

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

Para el cuerpo, citamos nuestra expresión para recibir una call :

 body < - quote(a + b) 

Una opción es convertir args a una pairlist y luego simplemente llamar a la función function usando eval :

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

Otra opción es crear una función vacía y luego llenarla con los valores deseados:

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

Una tercera opción es simplemente usar la función as.function :

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

Y, por último, esto me parece muy similar al primer método, excepto que estamos usando una expresión un tanto diferente para crear la llamada a la función, utilizando un substitute lugar de una 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 

También existe el problema de crear objetos alist programáticamente, ya que puede ser útil para crear funciones cuando el número de argumentos es variable.

Una alist es simplemente una lista con nombre de símbolos vacíos. Estos símbolos vacíos se pueden crear con substitute() . Asi que:

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