R 中的更高级别函数 - 是否有官方的 compose 运算符或 curry 函数?

发布于 2024-08-20 23:06:24 字数 1005 浏览 7 评论 0 原文

我可以在 R 中创建一个 compose 运算符:

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

像这样使用:

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

但我想知道是否有一组官方函数可以执行此类操作以及其他操作,例如 R 中的柯里化。很大程度上,这是为了减少我的代码中括号的数量、函数关键字等。

我的咖喱函数:

> 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 完成了吗?

I can create a compose operator in R:

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

To be used like this:

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

but I would like to know if there is an official set of functions to do this kind of thing and other operations such as currying in R. Largely this is to reduce the number of brackets, function keywords etc in my code.

My curry function:

> 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"

This is especially nice for e.g. aggregate:

> 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

Which I find much more elegant and editable than:

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

Basically I want to know - has this already been done for R?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

海的爱人是光 2024-08-27 23:06:25

roxygen 包中有一个名为 Curry 的函数。< br>
通过 R Mail Archive 上的此对话找到。

There is a function called Curry in the roxygen package.
Found via this conversation on the R Mail Archive.

甜心小果奶 2024-08-27 23:06:25

如果您希望变量的“名称”准确传递,则需要更复杂的方法。

例如,如果您执行 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 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())
    }
}

这很复杂,但我认为它是正确的。 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”的外部变量,不会出现问题。现在,所有变量都在调用 curry 的地方进行评估。

A more complex approach is required if you want the 'names' of the variables to pass through accurately.

For example, if you do plot(rnorm(1000),rnorm(1000)) then you will get nice labels on your x- and y- axes. Another example of this is 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

Not that the data.frame has assigned useful names to the columns.

Some implementations of Curry may not do this properly, leading to unreadable column names and plot labels. Instead, I now use something like this:

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())
    }
}

This is quite complex, but I think it's correct. match.call will catch all args, fully remembering what expressions defined the args (this is necessary for nice labels). The problem is that it catches too many args -- not just the ... but also the FUN. It also remembers the name of the function that's being called (Curry).

Therefore, we want to delete these first two entries in .orig so that .orig really just corresponds to the ... arguments. That's why we do .orig[[1]]<-NULL twice - each time deletes an entry and shifts everything else to the left.

This completes the definition and we can now do the following to get exactly the same as above

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

A final note on envir=parent.frame(). I used this to ensure that there won't be a problem if you have external variables called '.inner' or '.orig'. Now, all variables are evaluated in the place where the curry is called.

暖伴 2024-08-27 23:06:25

在 purrr 包中,现在有一个部分函数

in package purrr ,now there is a function partial

懒猫 2024-08-27 23:06:25

如果您已经在使用 tidyverse 的 purrr 软件包,则 purrr::partial 是柯里化函数的自然选择。从 purrr::partial 的描述来看:

# Partial is designed to replace the use of anonymous functions for
# filling in function arguments. Instead of:
compact1 <- function(x) discard(x, is.null)

# we can write:
compact2 <- partial(discard, .p = is.null)

If you are already using the purrr package from tidyverse, then purrr::partial is a natural choice to curry functions. From the description of purrr::partial:

# Partial is designed to replace the use of anonymous functions for
# filling in function arguments. Instead of:
compact1 <- function(x) discard(x, is.null)

# we can write:
compact2 <- partial(discard, .p = is.null)
偏爱自由 2024-08-27 23:06:24

这两个函数实际上都存在于 roxygen 包中 (在此处查看源代码”(最初基于 Byron Ellis 在 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 函数的用法,这在尝试进行函数式编程时非常有帮助有关更多详细信息,请参阅 ?Reduce(其中还涵盖其他函数,例如 MapFilter)。

还有你的 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 执行相同操作的方法(也可以使用 byaggregate 轻松完成,如已经所示):

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

Both of these functions actually exist in the roxygen package (see the source code here) from Peter Danenberg (was originally based on 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,
                       ...)
}

Note the usage of the Reduce function, which can be very helpful when trying to do functional programming in R. See ?Reduce for more details (which also covers other functions such as Map and Filter).

And your example of Curry (slightly different in this usage):

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

Here's an example to show the utility of Compose (applying three different functions to letters):

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

And your final example would work like this:

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

Lastly, here's a way to do the same thing with plyr (could also easily be done with by or aggregate as already shown):

> library(plyr)
> ddply(df, .(l), function(df) paste(toupper(df[,"t"]), collapse=""))
  l   V1
1 1  ABG
2 2 DEFH
3 3  CIJ
爱人如己 2024-08-27 23:06:24

R 中函数式编程的标准位置现在是函数库。

来自图书馆:

函数式:Curry、Compose 和其他高阶函数

示例:

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

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

PS:该库替代了ROxigen库。

The standard place for functional programming in R is now the functional library.

From the library:

functional: Curry, Compose, and other higher-order functions

Example:

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

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

PS: This library substitutes the ROxigen library.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文