默认情况下,在函数定义中使用 magrittr 点占位符通过管道运算符将对象传递给任意位置的参数?

Use magrittr dot placeholder IN FUNCTION DEFINITION to pass object via pipe operator to argument in arbitrary position by default?

我想以 magrittr 管道运算符默认将对象传递给第二个参数的方式定义一个函数。

library(magrittr)

foo <- function(a, b) c(a,b)

管道运算符将对象传递给 foo 的第一个参数。

1 %>% foo(2)

当使用 . 占位符时,管道将对象传递给 foo 的第二个参数。

1 %>% foo(2, .)

有没有办法构建一个函数,使其在定义中直接包含 . 占位符, 以便管道默认使用第二个参数? 在伪代码中这将是 类似于:

foo2 <- function(a, b = <pipe arg placeholder>) {
    b = <process arg placeholder> 
    c(a, b)
}

在此示例中,管道的输入用作 f 的第二个参数。我们确实必须使用 {...} 但也许这已经足够接近了。

f <- function(x, y = parent.frame()$.) x - y
12 %>% { f(20) }
## [1] 8

问题是 foo 没有 "know" 管道是如何写的 一旦 magrittr 添加了点(如果相关),它就知道如何调用它 所以从函数本身我们无法区分隐式和显式点。

撇开这个问题不谈,如果第一个参数是点并且 return 评估的修改调用:

,我们可以只切换第一个参数
library(magrittr)
foo <- function(a, b) {
  mc <- match.call()
  if(mc[[2]] == quote(.)) {
    mc[[2]] <- mc[[3]]
    mc[[3]] <- quote(.)
    return(eval.parent(mc))
  }
  c(a,b)
  }
1 %>% foo(2)
#> [1] 2 1
1 %>% foo(2, .)
#> [1] 2 1
# but also, because of disclaimer above
1 %>% foo(., 2)
#> [1] 2 1

reprex package (v0.3.0)

于 2019-10-09 创建

如果 a 可以采用默认值并留空,以及其他可能的边缘情况,则需要进行调整。

编辑:我说 foo 不知道管道是怎么写的,我在撒谎,它在调用堆栈中,我们可以通过在函数中调用 sys.call() 来查看它,但是我认为解决方案已经足够复杂了!


另一种方法是定义一个插入第二个位置的管道,它更灵活一点,可能也不那么令人惊讶:

foo <- function(a=2, b) {
  c(a,b)
}

`%>2%` <-
  function (lhs, rhs) {
    rhs_call <- insert_dot2(substitute(rhs))
    eval(rhs_call, envir = list(. = lhs), enclos = parent.frame())
  }

insert_dot2 <-
  function(expr, special_cases = TRUE) {
    if(is.symbol(expr) || expr[[1]] == quote(`(`)) {
      # if a symbol or an expression inside parentheses, make it a call with 
      # a missing first argument and a dot on second position
      expr <- as.call(c(expr,alist(x=)[[1]], quote(`.`)))
    } else if(length(expr) ==1) {
      # if a call without arg, same thing
      expr <- as.call(c(expr[[1]],alist(x=)[[1]], quote(`.`)))
    } else if (expr[[1]] != quote(`{`) &&
               all(sapply(expr[-1], `!=`, quote(`.`)))) {
      # if a call with args but no dot in arg, insert dot in second place first
      expr <- as.call(c(as.list(expr[1:2]), quote(`.`), as.list(expr[-(1:2)])))
    }
    expr
  }
1 %>2% foo(2)
#> [1] 2 1
1 %>2% foo(2, .)
#> [1] 2 1
1 %>2% foo(., 2)
#> [1] 1 2
1 %>2% foo()
#> [1] 2 1

reprex package (v0.3.0)

于 2019-10-09 创建

注意:管道到第二个有点奇怪,我宁愿管道到最后(对于你的问题的例子它会有相同的结果),如果你想管道到最后你会这样做:

foo <- function(a=2, b) {
  c(a,b)
}

`%>last%` <-
  function (lhs, rhs) {
    rhs_call <- insert_dot_last(substitute(rhs))
    eval(rhs_call, envir = list(. = lhs), enclos = parent.frame())
  }

insert_dot_last <-
  function(expr, special_cases = TRUE) {
    if(is.symbol(expr) || expr[[1]] == quote(`(`)) {
      # if a symbol or an expression inside parentheses, make it a call with 
      # a dot arg
      expr <- as.call(c(expr, quote(`.`)))
    } else if(length(expr) ==1) {
      # if a call without arg, same thing
      expr <- as.call(c(expr[[1]], quote(`.`)))
    } else if (expr[[1]] != quote(`{`) &&
               all(sapply(expr[-1], `!=`, quote(`.`)))) {
      # if a call with args but no dot in arg, insert dot in last place
      expr <- as.call(c(as.list(expr), quote(`.`)))
    }
    expr
  }
1 %>last% foo(2)
#> [1] 2 1
1 %>last% foo(2, .)
#> [1] 2 1
1 %>last% foo(., 2)
#> [1] 1 2

reprex package (v0.3.0)

于 2019-10-09 创建