管道表达式的求值顺序

编程语言 2026-07-09

我不理解管道表达式按什么顺序被求值。

请看下面的示例:

f1 <- function(x) {
  message("called f1")
  paste0(x, "_f1")
}

f2 <- function(x) {
  message("called f2")
  paste0(x, "_f2")
}

f3 <- function(x) {
  message("called f3")
  paste0(x, "_f3")
}

"a" %>% f1() %>% f2() %>% f3()

它的输出是:

called f3
called f2
called f1

并返回:

[1] "a_f1_f2_f3"

你能否准确解释表达式是如何被求值的?

哪个函数先被调用,哪个第二个被调用,哪个最后被调用?

解决方案

Expanding on comments:

What function is called first?

f3 会首先被调用。正如在 https://magrittr.tidyverse.org/#basic-piping:

 -  `x %>% f` is equivalent to `f(x)`
 -  `x %>% f(y)` is equivalent to `f(x, y)`
 -  `x %>% f %>% g %>% h` is equivalent to `h(g(f(x)))`

Here, “equivalent” is not technically exact: evaluation is non-standard, and 
the left-hand side is evaluated before passed on to the right-hand side 
expression. However, in most cases this has no practical implication.

这意味着在你的情况下,f3() 首先被调用,这解释了为什么我们先看到 "called f3"。 (第二次调用是 f2(),最后是 f1()。)

基底管道 |> 使它更接近“等价”:因为 |> 作为语法转换,它被转换成一个f3(f2(f1("a")))(如 @shs所说,@Friede进一步演示),然后再被解析。与 %>% 相比,在那里,`%>%`(..) 这个特殊函数会把表达式添加到执行栈中。

换句话说,第一条表达式(magrittr)被原样发送给解析器,带有 `%>%` special (infix) function between each other expression. The second expression (base pipe) is transformed before being sent to the parser. (Because of this, |> is expected to be a little faster than %>%

quote("a" %>% f1() %>% f2() %>% f3())
# "a" %>% f1() %>% f2() %>% f3()
quote("a" |> f1() |> f2() |> f3())
# f3(f2(f1("a")))

Can you please explain exactly how the expression is evaluated?

(嵌套的项目符号表示当前执行“栈”的层级。)

  • 进入 f3,调用 message("called f3"),然后调用 paste0(x, "_f3")
  • 其中一个参数尚未真正求值,因此它尝试求值 x,它被定义为来自 f2(f1("a")) 的结果。
  • 进入 f2,调用 message("called f2"),然后调用 paste0(x, "_f2")
  • 其中一个参数尚未真正求值,因此它尝试求值 x,它被定义为来自 f1("a") 的结果。
    • 进入 f1,调用 message("called f1"),然后调用 paste0(x, "_f1")
    • 所有参数都已真正求值并且存在,x 被定义为 "a",因此 paste() 会被求值,其结果是来自 f1() 的返回值。
  • 一旦 f1("a") 返回,对 paste() 的调用就可以完成,其结果是来自 f2() 的返回值。
  • 一旦 f2(f1("a")) 返回,对 paste() 的调用就可以完成,其结果是来自 f3() 的返回值。

重复使用极其相似的表达式和相同的参数可能会有点让人困惑,但我认为这已经把执行顺序讲清楚了。

顺便说一句,使用基础管道 |> 时也有类似的效果。


如前所述,消息看起来似乎顺序错乱的原因是由于 x 的惰性求值。设想如果不是 "a",而是一个“更耗时的操作”:

{ Sys.sleep(3); "a"; } %>% f1() %>% f2() %>% f3() 
# called f3
# called f2
# called f1
##### now it sleeps for a few seconds before returning
# [1] "a_f1_f2_f3"

这表明我们进入 f3f2f1,只有在那之后,R才会尝试确定 x 参数的值。这也是为什么当一个函数不使用某个参数时,它的状态或副作用永远不会被真正实现。

fun <- function(a=1, b=2) a

fun(a = stop("he"))
# Error in fun(a = stop("he")) (from #1) : he
fun(b = stop("he"))
# [1] 1

fun(a = Sys.sleep(3))
### three second sleep, then returns NULL
fun(b = Sys.sleep(3))
### immediately return, no sleep
# [1] 1
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章