3

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

GKi
  • 37,245
  • 2
  • 26
  • 48

1 Answers1

7

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

MrFlick
  • 195,160
  • 17
  • 277
  • 295
  • this is still confusing (to me). How does ``:=`` <- ``%>%`` not completely replace any behavior of the `data.table::``:=`` ` function? – Carl Witthoft May 02 '23 at 17:33
  • Continued - so the R precedence "parsing" happens even before looking to see what the actual function definition is? If I create any function at all with ":" in the name, this sort of thing will take place? – Carl Witthoft May 02 '23 at 17:47
  • 1
    The `data.table` package defines it's own `:=` operator that's not in global scope. Since they use non-standard evaluation, they can evaluate expressions is a special environment so that the global `:=` is never used. – MrFlick May 02 '23 at 18:03
  • 1
    @CarlWitthoft and for the second point, yes, parsing happens and the abstract syntax tree is created without any regard for what the function does. The symbols themselves determine precedence. If you define a function named `:`, it will get the same precedence at the native `:`. But there is no rule that allows you to create any function with `:` in the name. The `:=` is a very special case in the R parser. It is different than the `%any%` type operators you can define yourself. – MrFlick May 02 '23 at 19:17