Protect yourself from equals assignment!

A man sits at a table with a banner that has on it R's left assignment operator (<-) and underneath it says 'change my mind'.

tl;dr

I present you a function that warns if an R script contains The Assignment Operator That Shall Not Be Named.

Assign of the times

So, it’s been confirmed with extremely robust and objective evidence: the left-assignment arrow (x <- 1) is better than equals (x = 1) for assignment in R.1

So, unless you hate democracy, you should protect yourself from aberrant code that uses the cursed symbol.

But what if a nefarious colleague still sends you their scuffed code?

Assignment refinement

I’ve created the appraise_assignment() function that will peek at a suspect script and warn you if it contains the foul mark.

appraise_assignment <- function(file, destroy = FALSE) {
  
  tokens <- getParseData(parse(file))[["token"]]
  
  if (any(tokens == "EQ_ASSIGN")) {            # if '='
    warning("\nme = 'disgusted'") 
    if (destroy == TRUE) {
      answer <- readline("Destroy file? y/n: ")
      if (answer == "y") cat("Have mercy! This time...")
    }
  } else if (any(tokens == "RIGHT_ASSIGN")) {  # if '<-'
    cat("'unorthodox' -> you\n")
  } else if (any(tokens == "LEFT_ASSIGN")) {   # if '->'
    cat("you <- 'hero'\n")
  } else {
    cat("anyway(assignment(is(even('what'))))\n")
  }
  
}

Basically, we parse() an input file and then the function uses getParseData() to extract ‘tokens’ (i.e. maths symbols, special operators, variables, etc) from the R expressions within.

In particular, it spots the token called EQ_ASSIGN, which is when = is used in the context of assignment.

I saw the assign

For demonstration purposes, I’ve written four temporary files containing left assign (<-), right assign (->), equals (=), and no assignment at all.2 Our function will catch even a single deviation in a given file.

temp <- tempdir()  # temp location to store files

purrr::walk2(
  c("x <- 1", "x <- 1; y -> 1", "x <- 1; y = 1", "x"),
  c("left", "right", "equals", "none"),
  ~writeLines(.x, file.path(temp, paste0(.y, ".R")))
)

list.files(temp, pattern = ".R$")
## [1] "equals.R" "left.R"   "none.R"   "right.R"

First, let’s pass the file containing the unquestionably correct assignment operator.

appraise_assignment(file.path(temp, "left.R"))
## you <- 'hero'

Right-assignment is left-assignment’s less-handsome sibling.

appraise_assignment(file.path(temp, "right.R"))
## 'unorthodox' -> you

Hold steady…

appraise_assignment(file.path(temp, "equals.R"))
## Warning in appraise_assignment(file.path(temp, "equals.R")): 
## me = 'disgusted'

Phew, we got a warning, so we know the file is dangerous and should never be opened.

In fact, if you set the argument destroy = TRUE in appraise_assignment(), you’ll be prompted to irrecoverably annihilate the rotten file forever.3

For completeness, is it really an R script if it doesn’t contain any assignment at all?

appraise_assignment(file.path(temp, "none.R"))
## anyway(assignment(is(even('what'))))

Assigning off

In conclusion, some assignment operators were created more equal than others. See Colin Fay’s round-up to learn more about the history and plethora of these symbols (and be happy that the underscore is no longer legitimate).

Anyway, welcome to the best timeline, where we all recognise <- unequivocally as the champion and = can get absolutely rekt.

If I had one wish though, it would be to make the left-assign arrow even more powerful. How about making it really long? 23 hyphens seems sufficiently dominant.

x <----------------------- 1
x
## [1] 1

It’s a really long arrow, so I call it ‘the spear’.4 I look forward to its adoption by R Core.


Session info
## ─ Session info ───────────────────────────────────────────────────────────────
##  setting  value                       
##  version  R version 4.1.0 (2021-05-18)
##  os       macOS Big Sur 10.16         
##  system   x86_64, darwin17.0          
##  ui       X11                         
##  language (EN)                        
##  collate  en_GB.UTF-8                 
##  ctype    en_GB.UTF-8                 
##  tz       Europe/London               
##  date     2021-09-26                  
## 
## ─ Packages ───────────────────────────────────────────────────────────────────
##  package      * version date       lib source        
##  blogdown       1.4     2021-07-23 [1] CRAN (R 4.1.0)
##  bookdown       0.23    2021-08-13 [1] CRAN (R 4.1.0)
##  bslib          0.2.5.1 2021-05-18 [1] CRAN (R 4.1.0)
##  callr          3.7.0   2021-04-20 [1] CRAN (R 4.1.0)
##  cli            3.0.1   2021-07-17 [1] CRAN (R 4.1.0)
##  crayon         1.4.1   2021-02-08 [1] CRAN (R 4.1.0)
##  cyclocomp      1.1.0   2016-09-10 [1] CRAN (R 4.1.0)
##  desc           1.3.0   2021-03-05 [1] CRAN (R 4.1.0)
##  digest         0.6.27  2020-10-24 [1] CRAN (R 4.1.0)
##  evaluate       0.14    2019-05-28 [1] CRAN (R 4.1.0)
##  htmltools      0.5.1.1 2021-01-22 [1] CRAN (R 4.1.0)
##  jquerylib      0.1.4   2021-04-26 [1] CRAN (R 4.1.0)
##  jsonlite       1.7.2   2020-12-09 [1] CRAN (R 4.1.0)
##  knitr          1.34    2021-09-09 [1] CRAN (R 4.1.0)
##  lazyeval       0.2.2   2019-03-15 [1] CRAN (R 4.1.0)
##  lintr          2.0.1   2020-02-19 [1] CRAN (R 4.1.0)
##  magrittr       2.0.1   2020-11-17 [1] CRAN (R 4.1.0)
##  processx       3.5.2   2021-04-30 [1] CRAN (R 4.1.0)
##  ps             1.6.0   2021-02-28 [1] CRAN (R 4.1.0)
##  purrr          0.3.4   2020-04-17 [1] CRAN (R 4.1.0)
##  R6             2.5.1   2021-08-19 [1] CRAN (R 4.1.0)
##  remotes        2.4.0   2021-06-02 [1] CRAN (R 4.1.0)
##  rex            1.2.0   2020-04-21 [1] CRAN (R 4.1.0)
##  rlang          0.4.11  2021-04-30 [1] CRAN (R 4.1.0)
##  rmarkdown      2.10    2021-08-06 [1] CRAN (R 4.1.0)
##  rprojroot      2.0.2   2020-11-15 [1] CRAN (R 4.1.0)
##  rstudioapi     0.13    2020-11-12 [1] CRAN (R 4.1.0)
##  sass           0.4.0   2021-05-12 [1] CRAN (R 4.1.0)
##  sessioninfo    1.1.1   2018-11-05 [1] CRAN (R 4.1.0)
##  stringi        1.7.4   2021-08-25 [1] CRAN (R 4.1.0)
##  stringr        1.4.0   2019-02-10 [1] CRAN (R 4.1.0)
##  withr          2.4.2   2021-04-18 [1] CRAN (R 4.1.0)
##  xfun           0.26    2021-09-14 [1] CRAN (R 4.1.0)
##  xml2           1.3.2   2020-04-23 [1] CRAN (R 4.1.0)
##  xmlparsedata   1.0.5   2021-03-06 [1] CRAN (R 4.1.0)
##  yaml           2.2.1   2020-02-01 [1] CRAN (R 4.1.0)
## 
## [1] /Library/Frameworks/R.framework/Versions/4.1/Resources/library

  1. Actually, I don’t really care which one you use, but that’s less of a funny take. I prefer the left assignment operator because look! It’s a little arrow! Quirky! Esoteric! An extra keystroke to exercise your fingers!↩︎

  2. We do not talk about <<-.↩︎

  3. Well, not really, because I don’t want you to delete any of your files. But rest assured I’ve included file.remove() in my local version of the function and I’m not afraid to use it.↩︎

  4. In other words, R evaluates this as an object, x, being assigned a numeric value that has an odd number of ‘negative’ symbols that cancel each other out.↩︎