管道表达式的求值顺序
我不理解管道表达式按什么顺序被求值。
请看下面的示例:
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"
这表明我们进入 f3、f2 和 f1,只有在那之后,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