Функции более высокого уровня в R - есть ли официальный оператор оператора или функция карри?
Я могу создать оператор компоновки в R:
`%c%` = function(x,y)function(...)x(y(...))
Используется следующим образом:
> numericNull = is.null %c% numeric
> numericNull(myVec)
[2] TRUE FALSE
но я хотел бы знать, есть ли официальный набор функций для выполнения такого рода вещей и других операций, таких как currying в R. В большинстве случаев это уменьшает количество скобок, ключевых слов функции и т.д. в моем коде.
Моя функция 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"
Это особенно хорошо для, например, агрегат:
> 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
Что я нахожу намного более изящным и редактируемым, чем:
> aggregate(df$t, df["l"], function(x)paste(collapse="",toupper(x)))
l x
1 1 ADG
2 2 BCH
3 3 EFIJ
В принципе, я хочу знать - это уже сделано для R?
Ответы
Ответ 1
Стандартное место для функционального программирования в R теперь является библиотекой functional
.
Из библиотеки:
function: Curry, Compose и другие функции более высокого порядка
Пример:
library(functional)
newfunc <- Curry(oldfunc,x=5)
КРАН:
https://cran.r-project.org/web/packages/functional/index.html
PS: Эта библиотека заменяет библиотеку ROxigen
.
Ответ 2
Обе эти функции фактически существуют в пакете roxygen
(см. исходный код здесь) от Peter Danenberg (первоначально был основан на решении Байрона Эллиса в 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,
...)
}
Обратите внимание на использование функции Reduce
, что может быть очень полезно при попытке выполнить функциональное программирование в R. См. "Уменьшение" для более подробной информации (в которой также рассматриваются другие функции, такие как Map
и Filter
).
И ваш пример Curry (немного отличается в этом использовании):
> library(roxygen)
> p <- Curry(paste, collapse="")
> p(letters[1:10])
[1] "abcdefghij"
Вот пример, чтобы показать полезность Compose
(применяя три разных функции к буквам):
> Compose(function(x) x[length(x):1], Curry(paste, collapse=""), toupper)(letters)
[1] "ZYXWVUTSRQPONMLKJIHGFEDCBA"
И ваш последний пример будет работать следующим образом:
> aggregate(df[,"t"], df["l"], Compose(Curry(paste, collapse=""), toupper))
l x
1 1 ABG
2 2 DEFH
3 3 CIJ
Наконец, здесь можно сделать то же самое с plyr
(также можно легко сделать с помощью by
или aggregate
, как уже показано):
> library(plyr)
> ddply(df, .(l), function(df) paste(toupper(df[,"t"]), collapse=""))
l V1
1 1 ABG
2 2 DEFH
3 3 CIJ
Ответ 3
В пакете roxygen есть функция Curry.
Найденный через этот разговор в R Mail Archive.
Ответ 4
Более сложный подход необходим, если вы хотите, чтобы "имена" переменных проходили точно.
Например, если вы выполняете plot(rnorm(1000),rnorm(1000))
, вы получите яркие метки на оси x и y. Другим примером этого является 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
Не то, чтобы data.frame назначил полезные имена столбцам.
Некоторые реализации Curry могут не делать этого правильно, что приводит к нечитаемым именам столбцов и меткам графика. Вместо этого теперь я использую что-то вроде этого:
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())
}
}
Это довольно сложно, но я считаю это правильным. match.call
поймает все аргументы, полностью запоминая, какие выражения определяют аргументы (это необходимо для ярких меток). Проблема в том, что он ловит слишком много аргументов, а не только ...
, но также и FUN
. Он также запоминает имя вызываемой функции (Curry
).
Поэтому мы хотим удалить эти первые две записи в .orig
, чтобы .orig
действительно соответствовал аргументам ...
. Вот почему мы делаем .orig[[1]]<-NULL
дважды - каждый раз удаляет запись и сдвигает все остальное влево.
Это завершает определение, и теперь мы можем сделать следующее, чтобы получить то же самое, что и выше
Curry(data.frame, rnorm(5), rnorm(5) )( first=rpois(5,1) , second=rbinom(5,1,0.5) )
Последнее замечание на envir=parent.frame()
. Я использовал это, чтобы гарантировать, что не будет проблем, если у вас есть внешние переменные, называемые ".inner" или ".orig". Теперь все переменные оцениваются в том месте, где вызывается карри.