Here is another attempt to explain the distinctions. Please consider the following 3 examples.
library("dplyr")
mtcars %>%
mutate(newcol := mpg) %>%
mutate(newcol := newcol + 1) %>%
head() # works
SYMBOL <- rlang::sym("newcol")
mtcars %>%
mutate(!!SYMBOL := mpg) %>%
mutate(!!SYMBOL := !!SYMBOL + 1) %>%
head() # works
SYMSTR <- "newcol"
mtcars %>%
mutate(!!SYMSTR := mpg) %>%
mutate(!!SYMSTR := !!SYMSTR + 1) %>%
head() # errors-out
The third one (SYMSTR) errors out. This means that strings are not shorthands for symbols (fair enough). The issue is: the mental model that "!!" substitutes in newcol is wrong, it in fact substitutes in "newcol" (notice the quotes).
What saves a lot of examples is dplyr::mutate() is willing to accept quoted strings on the left-hand-sides of assignments (it does not insist on un-quoted symbols).
mtcars %>%
mutate("newcol" := mpg) %>%
mutate("newcol" := newcol + 1) %>%
head() # works
mtcars %>%
mutate("newcol" := mpg) %>%
mutate("newcol" := "newcol" + 1) %>%
head() # errors-out
Additional complexity is from the fact "!!" is willing to substitute both names (column names, names of variables and so on) and also substitute values (strings, possibly numbers). This is obviously confusing some users (as some expect only name substitutions).
Our function wrapr::let() tries to stay closer to a names-only substitution model.
library("wrapr")
library("dplyr")
let(
c(NEWCOL = "newcol"),
mtcars %>%
mutate(NEWCOL = mpg) %>%
mutate(NEWCOL = NEWCOL + 1) %>%
head()
)
We have a formal write up on wrapr::let() here.