2010-02-09 12 views
35

puedo crear un operador de composición en R:funciones de nivel superior en R - ¿hay un operador de redacción oficial o función de curry?

`%c%` = function(x,y)function(...)x(y(...)) 

Para usarse como esto:

> numericNull = is.null %c% numeric 
> numericNull(myVec) 
[2] TRUE FALSE 

pero me gustaría saber si hay un conjunto oficial de funciones para hacer este tipo de cosa y otras operaciones como currying in R. En gran medida esto es para reducir el número de corchetes, palabras clave de función, etc. en mi código.

Mi función de curry:

> curry=function(...){ 
    z1=z0=substitute(...);z1[1]=call("list"); 
    function(...){do.call(as.character(z0[[1]]), 
          as.list(c(eval(z1),list(...))))}} 
> p = curry(paste(collapse="")) 
> p(letters[1:10]) 
[1] "abcdefghij" 

Esto es especialmente agradable para, por ejemplo, agregada:

> df = data.frame(l=sample(1:3,10,rep=TRUE), t=letters[1:10]) 
> aggregate(df$t,df["l"],curry(paste(collapse="")) %c% toupper) 
    l x 
1 1 ADG 
2 2 BCH 
3 3 EFIJ 

que me parece mucho más elegante y editable que:

> aggregate(df$t, df["l"], function(x)paste(collapse="",toupper(x))) 
    l x 
1 1 ADG 
2 2 BCH 
3 3 EFIJ 

Básicamente quiero saber - esto ha sido ya realizada con el R?

+0

tenga en cuenta que mi llamada al curry actualmente no vincula las variables al curry del entorno donde se invoca, pero desde donde se invoca la función devuelta. Estoy trabajando en eso. –

+2

Si solo quieres una función de pegar con mejores valores predeterminados, ¿para qué molestarse con el currying? Solo defina, p. 'paste0 <- function (x, ...) paste (toupper (x), collapse =" ", ...)' Luego puede llamar a 'aggregate (df $ t, df [" l "], paste0)' –

+4

porque se vuelve más complejo cuando desea apilar más funciones. –

Respuesta

5

El lugar estándar para la programación funcional en R ahora es la biblioteca functional.

De la biblioteca:

funcional: Curry, componer, y otras funciones de orden superior

Ejemplo:

library(functional) 
    newfunc <- Curry(oldfunc,x=5) 

CRAN: https://cran.r-project.org/web/packages/functional/index.html

PS : Este sustituto de la biblioteca Utes la biblioteca ROxigen.

2

Hay una función llamada Curry en el paquete roxygen.
Se encontró a través de this conversation en el Archivo de correo de R.

+0

Lamentablemente, el enlace no funciona. Probablemente sea esto: https://stat.ethz.ch/pipermail/r-help/2009-December/221224.html –

+0

@FlorianJenn Gracias; He actualizado el enlace en la respuesta. –

28

Ambas funciones realmente existen en the roxygen package (see the source code here) de Peter Danenberg (se estableció inicialmente mediante Byron Ellis's solution on R-Help):

Curry <- function(FUN,...) { 
    .orig = list(...); 
    function(...) do.call(FUN,c(.orig,list(...))) 
} 

Compose <- function(...) { 
    fs <- list(...) 
    function(...) Reduce(function(x, f) f(x), 
         fs, 
         ...) 
} 

Nota el uso de la función Reduce, que puede ser muy útil cuando se trata de hacer programación funcional en R. Consulte? Reducir para obtener más detalles (que también cubre otras funciones como Map y Filter).

Y el ejemplo de Curry (ligeramente diferente en este uso):

> library(roxygen) 
> p <- Curry(paste, collapse="") 
> p(letters[1:10]) 
[1] "abcdefghij" 

He aquí un ejemplo para mostrar la utilidad de Compose (aplicando tres funciones diferentes a las letras):

> Compose(function(x) x[length(x):1], Curry(paste, collapse=""), toupper)(letters) 
[1] "ZYXWVUTSRQPONMLKJIHGFEDCBA" 

Y su ejemplo final funcionaría así:

> aggregate(df[,"t"], df["l"], Compose(Curry(paste, collapse=""), toupper)) 
    l x 
1 1 ABG 
2 2 DEFH 
3 3 CIJ 

Por último, he aquí una manera de hacer lo mismo con plyr (también podría fácilmente hacerse con by o aggregate como ya se ha mostrado):

> library(plyr) 
> ddply(df, .(l), function(df) paste(toupper(df[,"t"]), collapse="")) 
    l V1 
1 1 ABG 
2 2 DEFH 
3 3 CIJ 
+2

¿No hay algo de malo en usar un paquete para la programación de lectura y escritura para la programación funcional? ¿Qué dice eso sobre la modularidad de R libs? – piccolbo

+9

FWIW, 'Compose' y' Curry' se movieron al paquete 'functional' hace un rato. –

2

Se requiere un enfoque más complejo si desea que los 'nombres' de las variables para pasar con precisión.

Por ejemplo, si hace plot(rnorm(1000),rnorm(1000)), obtendrá bonitas etiquetas en sus ejes xey. Otro ejemplo de esto es data.frame

> data.frame(rnorm(5), rnorm(5), first=rpois(5,1), second=rbinom(5,1,0.5)) 
    rnorm.5. rnorm.5..1 first second 
1 0.1964190 -0.2949770  0  0 
2 0.4750665 0.8849750  1  0 
3 -0.7829424 0.4174636  2  0 
4 1.6551403 1.3547863  0  1 
5 1.4044107 -0.4216046  0  0 

No es que el hoja.de.datos ha asignado nombres útiles a las columnas.

Algunas implementaciones de Curry pueden no hacer esto correctamente, lo que lleva a nombres de columnas ilegibles y etiquetas de trazado. En cambio, ahora utilizo algo como esto:

Curry <- function(FUN, ...) { 
    .orig = match.call() 
    .orig[[1]] <- NULL # Remove first item, which matches FUN 
    .orig[[1]] <- NULL # Remove another item, which matches Curried argument 
    function(...) { 
     .inner = match.call() 
     .inner[[1]] <- NULL # Remove first item, which matches Curry 
     do.call(FUN, c(.orig, .inner), envir=parent.frame()) 
    } 
} 

Esto es bastante complejo, pero creo que es correcto. match.call capturará todos los argumentos, recordando completamente qué expresiones definieron los argumentos (esto es necesario para etiquetas agradables). El problema es que atrapa demasiados argumentos, no solo el ... sino también el FUN. También recuerda el nombre de la función a la que se llama (Curry).

Por lo tanto, queremos eliminar estas dos primeras entradas en .orig para que .orig realmente se corresponda con los argumentos .... Es por eso que hacemos .orig[[1]]<-NULL dos veces - cada vez borra una entrada y cambia todo lo demás a la izquierda.

Esto completa la definición y que ahora puede hacer lo siguiente para obtener exactamente el mismo que el anterior

Curry(data.frame, rnorm(5), rnorm(5))(first=rpois(5,1) , second=rbinom(5,1,0.5)) 

Una nota final sobre envir=parent.frame(). Lo usé para asegurarme de que no haya un problema si tienes variables externas llamadas '.inner' o '.orig'. Ahora, todas las variables se evalúan en el lugar donde se llama el curry.

+0

(Contestador aquí) Acabo de inventar esto hoy. Supongo que ya se ha hecho? Y tal vez hay un error que no sé? –

Cuestiones relacionadas