Как я могу написать рекурсивную функцию compose в R?
Я хотел бы создать функцию "compose" в R, которая будет составлять произвольное число функций, заданных в качестве аргументов.
До сих пор я достиг этого, определив функцию "of", которая составляет два аргумента, а затем уменьшив ее:
of <- function(f,g) function(x) f(g(x))
id <- function(x) x
compose <- function(...) {
argms = c(...)
Reduce(of,argms,id)
}
Это, кажется, работает нормально, но так как я изучаю R, я подумал, что попробую написать его в явном рекурсивном стиле, то есть отказаться от использования Reduce, iow то, что вы сделали бы в схеме, подобной это:
(define (compose . args)
(if (null? args) identity
((car args) (apply compose (cdr args)))))
Я столкнулся с рядом препятствий, главным из которых на данный момент, по-видимому, является то, что первый элемент аргументов не распознается как функция. Моя слабая попытка до сих пор:
comp <- function(...) {
argms <- list(...)
len <- length(argms)
if(len==0) { return(id) }
else {
(argms[1])(do.call(comp,argms[2:len]))
}
}
Выплевывает: Error in comp(sin, cos, tan) : attempt to apply non-function
5 ответов:
1) Попробуйте это:
comp1 <- function(f, ...) { if (missing(f)) identity else function(x) f(comp1(...)(x)) } # test comp1(sin, cos, tan)(pi/4) ## [1] 0.5143953 # compose is defined in the question compose(sin, cos, tan)(pi/4) ## [1] 0.5143953 functional::Compose(tan, cos, sin)(pi/4) ## [1] 0.5143953 sin(cos(tan(pi/4))) ## [1] 0.5143953 library(magrittr) (pi/4) %>% tan %>% cos %>% sin ## [1] 0.5143953 (. %>% tan %>% cos %>% sin)(pi/4) ## [1] 0.5143953
1A) вариация (1), которая использует
Recall
:comp1a <- function(f, ...) { if (missing(f)) identity else { fun <- Recall(...) function(x) f(fun(x)) } } comp1a(sin, cos, tan)(pi/4) ## [1] 0.5143953
2) Вот еще одна реализация:
comp2 <- function(f, g, ...) { if (missing(f)) identity else if (missing(g)) f else Recall(function(x) f(g(x)), ...) } comp2(sin, cos, tan)(pi/4) ## [1] 0.5143953
3) эта реализация ближе к коду в вопросе. Он использует
of
, определенные в вопросе:comp3 <- function(...) { if(...length() == 0) identity else of(..1, do.call("comp3", list(...)[-1])) } comp3(sin, cos, tan)(pi/4) ## [1] 0.5143953
Одна из проблем заключается в том, что если
len==1
, тоargms[2:len]
возвращает список длины 2; в частности,> identical(argms[2:1], list(NULL, argms[[1]])) [1] TRUE
Чтобы исправить это, вы можете просто удалить первый элемент списка, используя
argms[-1]
.Вам также нужно использовать вашу функцию
of
, потому что, как вы, вероятно, заметили,sin(cos)
возвращает ошибку, а не функцию. Собрав все это вместе, мы получим:comp <- function(...) { argms <- c(...) len <- length(argms) if(len==1) { return(of(argms[[1]], id)) } else { of(argms[[1]], comp(argms[-1])) } } > comp(sin, cos, tan)(1) [1] 0.0133878 > compose(sin, cos, tan)(1) [1] 0.0133878
Альтернативой свертыванию собственной композиции функций является использование пакета gestalt, который обеспечивает композицию как в виде функции более высокого порядка
compose()
, так и в виде оператора инфикса%>>>%
. (Чтобы они читались одинаково, функции составляются из слева направо.)Основное использование является простым:
library(gestalt) f <- compose(tan, cos, sin) # apply tan, then cos, then sin f(pi/4) #> [1] 0.514395258524 g <- tan %>>>% cos %>>>% sin g(pi/4) #> [1] 0.514395258524
Но вы получаете много дополнительной гибкости:
## You can annotate composite functions and apply list methods f <- first: tan %>>>% cos %>>>% sin f[[1]](pi/4) #> [1] 1 f$first(pi/4) #> [1] 1 ## magrittr %>% semantics, such as implicity currying, is supported scramble <- sample %>>>% paste(collapse = "") set.seed(1); scramble(letters, 5) #> [1] "gjnue" ## Compositions are list-like; you can inspect them using higher-order functions stepwise <- lapply(`%>>>%`, print) %>>>% compose stepwise(f)(pi/4) #> [1] 1 #> [1] 0.540302305868 #> [1] 0.514395258524 ## formals are preserved identical(formals(scramble), formals(sample)) #> [1] TRUE
Одна вещь, которую вы должны иметь в виду о вызовах функций в R, это то, что их стоимость равна не так уж и незначительно. В отличие от выполнения композиции литеральных функций,
compose()
(и%>>>%
) сглаживают композиции при вызове. В частности, следующие вызовы производят ту же самую функцию, операционально :fs <- list(tan, cos, sin) ## compose(tan, cos, sin) Reduce(compose, fs) Reduce(`%>>>%`, fs) compose(fs) compose(!!!fs) # tidyverse unquote-splicing
Вот решение, которое возвращает функцию, которую легко понять
func <- function(f, ...){ cl <- match.call() if(length(cl) == 2L) return(eval(bquote(function(...) .(cl[[2L]])))) le <- max(which(sapply(cl, inherits, "name"))) if(le == length(cl)){ tmp <- cl[le] tmp[[2L]] <- quote(...) cl[[length(cl)]] <- tmp } else if(le == length(cl) - 1L){ tmp <- cl[le] tmp[[2L]] <- cl[[le + 1L]] cl[[le]] <- tmp cl[[le + 1L]] <- NULL } else stop("something is wrong...") eval(cl) } func(sin, cos, tan) # clear what the function does #R function (...) #R sin(cos(tan(...))) #R <environment: 0x000000001a189778> func(sin, cos, tan)(pi/4) # gives correct value #R [1] 0.5143953
Возможно, придется подогнать строку
sapply(cl, inherits, "name")
к чему-то более общему...
Вот решение, которое строит функцию из вызовов, оно дает читаемый вывод, подобный выходу Бенджамина:
compose_explicit <- function(...){ funs <- as.character(match.call()[-1]) body <- Reduce(function(x,y) call(y,x), rev(funs), init = quote(x)) eval.parent(call("function",as.pairlist(alist(x=)),body)) } compose_explicit(sin, cos, tan) # function (x) # sin(cos(tan(x))) compose_explicit(sin, cos, tan)(pi/4) # [1] 0.5143953
Кажется вполне надежным:
compose_explicit() # function (x) # x compose_explicit(sin) # function (x) # sin(x)
И не связанные, но полезные, вот код
purrr:compose
:#' Compose multiple functions #' #' @param ... n functions to apply in order from right to left. #' @return A function #' @export #' @examples #' not_null <- compose(`!`, is.null) #' not_null(4) #' not_null(NULL) #' #' add1 <- function(x) x + 1 #' compose(add1, add1)(8) compose <- function(...) { fs <- lapply(list(...), match.fun) n <- length(fs) last <- fs[[n]] rest <- fs[-n] function(...) { out <- last(...) for (f in rev(rest)) { out <- f(out) } out } }