funções de nível superior em R - existe um operador oficial de composição ou função curry?
-
19-09-2019 - |
Pergunta
Posso criar um operador de composição em R:
`%c%` = function(x,y)function(...)x(y(...))
Para ser usado assim:
> numericNull = is.null %c% numeric
> numericNull(myVec)
[2] TRUE FALSE
mas gostaria de saber se existe um conjunto oficial de funções para fazer esse tipo de coisa e outras operações como curry em R.Em grande parte, isso é para reduzir o número de colchetes, palavras-chave de função, etc. em meu código.
Minha função 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"
Isto é especialmente bom para, por ex.agregar:
> 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
O que considero muito mais elegante e editável do que:
> aggregate(df$t, df["l"], function(x)paste(collapse="",toupper(x)))
l x
1 1 ADG
2 2 BCH
3 3 EFIJ
Basicamente eu quero saber - isso já foi feito para R?
Solução
O local padrão para programação funcional em r é agora o functional
biblioteca.
Da Biblioteca:
Funcional: curry, composição e outras funções de ordem superior
Exemplo:
library(functional)
newfunc <- Curry(oldfunc,x=5)
Cran:https://cran.r-project.org/web/packages/function/index.html
PS: Esta biblioteca substitui o ROxigen
biblioteca.
Outras dicas
Ambas as funções realmente existem em a roxygen
pacote (Veja o código -fonte aqui) de Peter Danenberg (foi originalmente baseado em Solução de Byron Ellis no 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,
...)
}
Observe o uso do Reduce
função, o que pode ser muito útil ao tentar fazer programação funcional em R. Veja? Reduza para obter mais detalhes (que também abrange outras funções, como Map
e Filter
).
E seu exemplo de curry (ligeiramente diferente neste uso):
> library(roxygen)
> p <- Curry(paste, collapse="")
> p(letters[1:10])
[1] "abcdefghij"
Aqui está um exemplo para mostrar a utilidade de Compose
(Aplicando três funções diferentes às cartas):
> Compose(function(x) x[length(x):1], Curry(paste, collapse=""), toupper)(letters)
[1] "ZYXWVUTSRQPONMLKJIHGFEDCBA"
E seu exemplo final funcionaria assim:
> aggregate(df[,"t"], df["l"], Compose(Curry(paste, collapse=""), toupper))
l x
1 1 ABG
2 2 DEFH
3 3 CIJ
Por fim, aqui está uma maneira de fazer a mesma coisa com plyr
(também poderia ser feito facilmente com by
ou aggregate
Como já mostrado):
> library(plyr)
> ddply(df, .(l), function(df) paste(toupper(df[,"t"]), collapse=""))
l V1
1 1 ABG
2 2 DEFH
3 3 CIJ
Há uma função chamada curry no Roxygen pacote.
Encontrado via esta conversa no arquivo de correio R.
Uma abordagem mais complexa é necessária se você quiser que os 'nomes' das variáveis sejam transmitidos com precisão.
Por exemplo, se você fizer plot(rnorm(1000),rnorm(1000))
então você obterá ótimos rótulos em seus eixos x e y.Outro exemplo disso é 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
Não que o data.frame tenha atribuído nomes úteis às colunas.
Algumas implementações de Curry podem não fazer isso corretamente, levando a nomes de colunas e rótulos de gráficos ilegíveis.Em vez disso, agora uso algo assim:
Curry <- function(FUN, ...) {
.orig = match.call()
.orig[[1]] <- NULL # Remove first item, which matches Curry
.orig[[1]] <- NULL # Remove another item, which matches FUN
function(...) {
.inner = match.call()
.inner[[1]] <- NULL # Remove first item, which matches Curry
do.call(FUN, c(.orig, .inner), envir=parent.frame())
}
}
Isso é bastante complexo, mas acho que está correto. match.call
irá capturar todos os argumentos, lembrando completamente quais expressões definiram os argumentos (isso é necessário para rótulos legais).O problema é que ele captura muitos argumentos - não apenas os ...
mas também o FUN
.Ele também lembra o nome da função que está sendo chamada (Curry
).
Portanto, queremos excluir essas duas primeiras entradas em .orig
para que .orig
realmente corresponde apenas ao ...
argumentos.É por isso que fazemos .orig[[1]]<-NULL
duas vezes - cada vez exclui uma entrada e desloca todo o resto para a esquerda.
Isso completa a definição e agora podemos fazer o seguinte para obter exatamente O mesmo que acima
Curry(data.frame, rnorm(5), rnorm(5) )( first=rpois(5,1) , second=rbinom(5,1,0.5) )
Uma nota final sobre envir=parent.frame()
.Usei isso para garantir que não haverá problema se você tiver variáveis externas chamadas '.inner' ou '.orig'.Agora, todas as variáveis são avaliadas no local onde o curry é chamado.