管道函数中的消息顺序

3
有人可以解释一下,为什么在以下函数被管道连接时,消息的顺序会颠倒?是否有办法避免这种行为,同时保留管道?
f1 <- function(x){
  message("message 1")
  x
}

f2 <- function(x){
  message("message 2")
  x
}

x = 1

x |>
  f1() |>
  f2()

输出:

message 2

message 1

[1] 1

如果使用 x |> map_dbl(f1) |> map_dbl(f2),则顺序是正确的... - danlooo
2个回答

2

管道的作用是使用LHS中的值调用RHS中的函数,例如:

mtcars |> head()

此管道执行函数head(mtcars)

在您的代码中,这将归结为f2(f1(x))

从逻辑上看,如果您以这种方式查看您的管道,则首先处理f2()f2()的作用是发送消息并调用x,在本例中,xf1(x)。只有在那一点上才会调用f1(),它发送消息然后调用x,在此函数中,x是1,该值将被打印。

如何规避这种情况?可以在发送消息之前调用x,并以return结束函数,如下所示:

f1 <- function(x){
  x
  message("message 1")
  return(x)
}

f2 <- function(x){
  x
  message("message 2")
  return(x)
}

x = 1

x |>
  f1() |>
  f2()

我没有测试过这个,但是评估x,然后调用return(x)不是多余的吗?至少在这种情况下是这样,因为每个函数的第一行实际上并没有评估任何东西。 - camille
在这个例子中,实际上并不是多余的,因为如果您不指定 return,函数将仅返回消息,因此您将获得正确的消息顺序,但您将错过末尾的 [1] 1 - koolmees
是的,我的意思是似乎你不需要在每个函数的第一行打印 x。如果函数只包含 message("message"); return(x),它不应该得到结果吗? - camille
我原以为这个操作等同于:x <- f1(x); f2(x),但事实并非如此,对吧? - Phil
我现在明白了,谢谢!所以这是由于惰性求值。f1(x)(而不是f1(x)的结果)作为参数传递给f2()。然而,在f2()内部,这个参数只有在“message 2”之后才被评估。 - Phil
显示剩余4条评论

1
与使用map包装f1和f2的版本相比,可以在AST中看到普通版本中没有额外节点,这可能是顺序未被考虑的原因。
library(lobstr)
library(magrittr)
library(purrr)
#> 
#> Attaching package: 'purrr'
#> The following object is masked from 'package:magrittr':
#> 
#>     set_names

f1 <- function(x){
  message("message 1")
  x
}

f2 <- function(x){
  message("message 2")
  x
}

x <- 1
x |> f1() |> f2()
#> message 2
#> message 1
#> [1] 1
x %>% f1() %>% f2()
#> message 2
#> message 1
#> [1] 1
x |> map_dbl(f1) |> map_dbl(f2)
#> message 1
#> message 2
#> [1] 1

ast(x |> f1() |> f2())
#> █─f2 
#> └─█─f1 
#>   └─x
ast(x %>% f1() %>% f2())
#> █─`%>%` 
#> ├─█─`%>%` 
#> │ ├─x 
#> │ └─█─f1 
#> └─█─f2
ast(x |> map_dbl(f1) |> map_dbl(f2))
#> █─map_dbl 
#> ├─█─map_dbl 
#> │ ├─x 
#> │ └─f1 
#> └─f2

reprex package (v2.0.1) 在 2021-09-10 创建


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接