I am using a function from @Konrad Rudolph but changed its name from %>%
to :=
and get for the same call different results.
`%>%` = function (lhs, rhs) {
subst = call('substitute', substitute(rhs), list(. = lhs))
eval.parent(eval(subst))
}
`:=` <- `%>%`
1 %>% .+2 %>% .*3
#[1] 7
1 := .+2 := .*3
#[1] 3
Or with a different function.
`%>%` <- function(lhs, rhs) {
assign(".", lhs, envir=parent.frame())
eval(substitute(rhs), parent.frame())
}
`:=` <- `%>%`
1 %>% .+2 %>% .*3
#[1] 7
1 := .+2 := .*3
#[1] 9
Why do I get other results when using the function named :=
?
The reason is operator precedence. We can use the lobstr
package to see the abstract syntax tree for the code.
lobstr::ast(1 %>% .+1 %>% .+2)
█─`+`
├─█─`+`
│ ├─█─`%>%`
│ │ ├─1
│ │ └─.
│ └─█─`%>%`
│ ├─1
│ └─.
└─2
vs
lobstr::ast(1 := .+1 := .+2)
█─`:=`
├─1
└─█─`:=`
├─█─`+`
│ ├─.
│ └─1
└─█─`+`
├─.
└─2
So when you run the %>%
version, the pipes are happening before the additions, but with the :=
version, the addition happens before the :=
If you add in the implicit parenthesis, you'll see that these two are equivalent
1 %>% .+1 %>% .+2
((1 %>% .)+(1 %>% .))+2
however the behavior you might expect would need to look like
1 %>% (.+1) %>% (.+2)
but still "works". And this is how the other expression breaks down
1 := .+1 := .+2
1 := ((.+1) := (.+2))
(1+1) := (1+2)
The way the function was defined, the middle term essentially disappears since the .
is replaced by the outer :=
so there are no free .
variables left for the inner =
The order of operations is defined on the ?Syntax
help page. You cannot change the precedence for functions without changing the R source code itself. While not listed explicitly on the page, :=
has the same precedence as <-
(it's aliased as a LEFT_ASSIGN in the parser).