Search code examples
rpipeoperator-precedence

Same function but using for it the name %>% causes a different result compared when using the name :=


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 :=?


Solution

  • 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).