30

R allows for assignment via <- and =.

Whereas there a subtle differences between both assignment operators, there seems to be a broad consensus that <- is the better choice than =, as = is also used as operator mapping values to arguments and thus its use may lead to ambiguous statements. The following exemplifies this:

> system.time(x <- rnorm(10))
   user  system elapsed 
      0       0       0 
> system.time(x = rnorm(10))
Error in system.time(x = rnorm(10)) : unused argument(s) (x = rnorm(10))

In fact, the Google style code disallows using = for assignment (see comments to this answer for a converse view).

I also almost exclusively use <- as assignment operator. However, the almost in the previous sentence is the reason for this question. When = acts as assignment operator in my code it is always accidental and if it leads to problems these are usually hard to spot.

I would like to know if there is a way to turn off assignment via = and let R throw an error any time = is used for assignment.

Optimally this behavior would only occur for code in the Global Environment, as there may well be code in attached namespaces that uses = for assignment and should not break.

(This question was inspired by a discussion with Jonathan Nelson)

Community
  • 1
  • 1
Henrik
  • 14,202
  • 10
  • 68
  • 91
  • I wish one could! But methinks you can't, unless you putz around with the parser. – Dirk Eddelbuettel Sep 13 '12 at 23:08
  • @DirkEddelbuettel It would have surprised me, if an answer would not involve heavily fiddling with R internals. But perhaps someone here knows how, or likes to find out how. As your comment indicates I am obviously not the only one thinking this might be great. – Henrik Sep 13 '12 at 23:12
  • 1
    Based on [this John Chambers post](http://developer.r-project.org/equalAssign.html), it looks you could have what you want, if only you were willing to use a pre-2001 version of R ;) – Josh O'Brien Sep 13 '12 at 23:16
  • @JoshO'Brien When reading the post it seems that John Chambers did foresee many potential problems associated with assignment via `=`. The question then is why it was implemented in the first place. The argument `This increases compatibility with S-Plus (as well as with C, Java, and many other languages).` does not really convince me giving the possible ambiguities. – Henrik Sep 13 '12 at 23:31
  • 3
    You can use the `tidy.source` function in the [formatR](http://yihui.name/formatR/) package to replace `=` with `<-` in source code from your clipboard or a file. – GSee Sep 13 '12 at 23:32
  • 1
    @Henrik -- Did you notice the bit in there about `_` -- the original `<-` operator? That's a pretty nifty bit of R-trivia right there. – Josh O'Brien Sep 13 '12 at 23:35
  • @JoshO'Brien I fully agree. It seems that ten years were enough time to "phase it out". `x _ 3` just throws an error now. – Henrik Sep 13 '12 at 23:39
  • Change keyboard layout. Accessing = on my keyboard is less handy than writing <-. – Roman Luštrik Sep 14 '12 at 08:07
  • 2
    As someone who teaches R **alot**, the `<-` is a pain. Out of a class of 20 "non-programming" academics, at least two people fall foul of the old {`x<-5`, `x< -5`} problem. How many published analysis do you think contains errors because of this issue? Your example above doesn't convince me that `=` is a problem, since you **almost never** want to assign variables in a function call. When you do, you want to be very clear why (now runs away and hides). – csgillespie Sep 14 '12 at 13:25

4 Answers4

37

Here's a candidate:

`=` <- function(...) stop("Assignment by = disabled, use <- instead")
# seems to work
a = 1
Error in a = 1 : Assignment by = disabled, use <- instead
# appears not to break named arguments
sum(1:2,na.rm=TRUE)
[1] 3
James
  • 65,548
  • 14
  • 155
  • 193
  • 1
    Can you set it this way and keep using packages that rely on `=` as an assignment operator? (One's coding standards are not necessarily everyone's...) – flodel Sep 14 '12 at 03:31
  • 8
    This shouldn't affect code from other packages. ``=`` here gets assigned into the global environment. While evaluating function `foo` from package **bar**, R's evaluator will search for the value of `=` first in **bar**'s namespace, *then* among the **bar** package's imports, and *then* in the `package:base`. There, it will find an unaltered version of `=`, before the search ever reaches `.GlobalEnv`. Pretty nifty, ennit? – Josh O'Brien Sep 14 '12 at 05:33
  • I agree with most voters, that this is the best answer. I only have one further point. The `=` function then shows up in `ls()`. It would be great if this would not be the case (ideally you would have it in your Rprofile.site and `ls()` shows nothing when starting a fresh R session). – Henrik Sep 14 '12 at 10:23
  • How about putting the function in it's own package and then loading the package in your `Rprofile.site`? – MattLBeck Sep 14 '12 at 11:30
  • @kikumbob This could be an option, if `package:base` is searched before other attached (but not imported) packages when evaluating function `foo` from package **bar**. Josh O'Brien's comments seems to say so, but I would have to check or better read some more on the order of searching in the search path. Does anyone have a good reference on the order of walking through the search path? – Henrik Sep 14 '12 at 13:16
  • Loading this in my `.Rprofile` causes RStudio to crash on startup without an error message--but R run from the console works just fine (Linux Ubuntu PP 64-bit). – Ari B. Friedman Sep 14 '12 at 14:51
  • @AriB.Friedman -- Did you try wrapping the `.Rprofile` edit in a `.First` function, like this: `.First <- function() { f <- function(...) stop("Assignment by = disabled, use <- instead") assign("=", f, .GlobalEnv) }` – Josh O'Brien Sep 14 '12 at 15:18
  • @JoshO'Brien That's probably it. I had it outside my .First block. Will give it a shot later. – Ari B. Friedman Sep 14 '12 at 15:29
  • 1
    @Henrik -- [This blog post](http://obeautifulcode.com/R/How-R-Searches-And-Finds-Stuff/) seems to have helped a lot of folks grok R's scoping/variable search strategy. [The official documentation](http://cran.r-project.org/doc/manuals/R-lang.html#Search-path) covers the same ground, but in ~1% as many words! Together, they should give you a good start. – Josh O'Brien Sep 14 '12 at 15:32
9

I'm not sure, but maybe simply overwriting the assignment of = is enough for you. After all, `=` is a name like any other—almost.

> `=` <- function() { }
> a = 3
Error in a = 3 : unused argument(s) (a, 3)
> a <- 3
> data.frame(a = 3)
  a
1 3

So any use of = for assignment will result in an error, whereas using it to name arguments remains valid. Its use in functions might go unnoticed unless the line in question actually gets executed.

MvG
  • 57,380
  • 22
  • 148
  • 276
7

The lint package (CRAN) has a style check for that, so assuming you have your code in a file, you can run lint against it and it will warn you about those line numbers with = assignments.

Here is a basic example:

temp <- tempfile()
write("foo = function(...) {
          good <- 0
          bad = 1
          sum(..., na.rm = TRUE)
       }", file = temp)

library(lint) 
lint(file = temp, style = list(styles.assignment.noeq))
# Lint checking: C:\Users\flodel\AppData\Local\Temp\RtmpwF3pZ6\file19ac3b66b81
# Lint: Equal sign assignemnts: found on lines 1, 3

The lint package comes with a few more tests you may find interesting, including:

  • warns against right assignments
  • recommends spaces around =
  • recommends spaces after commas
  • recommends spaces between infixes (a.k.a. binary operators)
  • warns against tabs
  • possibility to warn against a max line width
  • warns against assignments inside function calls

You can turn on or off any of the pre-defined style checks and you can write your own. However the package is still in its infancy: it comes with a few bugs (https://github.com/halpo/lint) and the documentation is a bit hard to digest. The author is responsive though and slowly making improvements.

flodel
  • 87,577
  • 21
  • 185
  • 223
3

If you don't want to break existing code, something like this (printing a warning not an error) might work - you give the warning then assign to the parent.frame using <- (to avoid any recursion)

`=` <- function(...){
        .what <- as.list(match.call())
        .call <-  sprintf('%s <- %s', deparse(.what[[2]]),  deparse(.what[[3]]))
        mess <- 'Use <- instead of = for assigment '
        if(getOption('warn_assign', default = T)) {
        stop (mess) } else {
        warning(mess)
        eval(parse(text =.call), envir = parent.frame())  
          }
        }

If you set options(warn_assign = F), then = will warn and assign. Anything else will throw an error and not assign.

examples in use

# with no option set
z = 1
## Error in z = 1 : Use <- instead of = for assigment 
options(warn_assign = T)
z = 1
## Error in z = 1 : Use <- instead of = for assigment 
 options(warn_assign = F)
 z = 1
## Warning message:
##  In z = 1 : Use <- instead of = for assigment 

Better options

I think formatR or lint and code formatting are better approaches.

mnel
  • 113,303
  • 27
  • 265
  • 254