I don't know about "canonical", but: this is one of the examples that illustrates how assignment (which can be interchangeably be done with <-
and =
) and passing named arguments (which can only be done using =
) are different. It's all about the context in which the expressions x <- y <- 10
or x = y = 10
are evaluated. On their own,
x <- y <- 10
x = y = 10
do exactly the same thing (there are few edge cases where =
and <-
aren't completely interchangeable as assignment operators, e.g. having to do with operator precedence). Specifically, these are evaluated as (x <- (y <- 10))
, or the equivalent with =
. y <- 10
assigns the value to 10, and returns the value 10; then x <- 10
is evaluated.
Although it looks similar, this is not the same as the use of =
to pass a named argument to a function. As noted by the OP, if f()
is a function, f(x = y = 10)
is not syntactically correct:
f <- function(x, y) {
x + y
}
f(x = y = 10)
## Error: unexpected '=' in "f(x = y ="
You might be tempted to say "oh, then I can just use arrows instead of equals signs", but this does something different.
f(x <- y <- 10)
## Error in f(x <- y <- 10) : argument "y" is missing, with no default
This statement tries to first evaluate the x <- y <- 10
expression (as above); once it works, it calls f()
with the result. If the function you are calling will work with a single, unnamed argument (as plot()
does), and you will get a result — although not the result you expect. In this case, since the function has no default value for y
, it throws an error.
People do sometimes use <-
with a function call as shortcut; in particular I like to use idioms like if (length(x <- ...) > 0) { <do_stuff> }
so I don't have to repeat the ...
later. For example:
if (length(L <- list(...))>0) {
warning(paste("additional arguments to ranef.merMod ignored:",
paste(names(L),collapse=", ")))
}
Note that the expression length(L <- list(...))>0)
could also be written as !length(L <- list(...))
(since the result of length()
must be a non-negative integer, and 0 evaluates to FALSE), but I personally think this is a bridge too far in terms of compactness vs readability ... I sometimes think it would be better to forgo the assignment-within-if
and write this as L <- list(...); if (length(L)>0) { ... }
PS forcing the association of assignment in the other order leads to some confusing errors, I think due to R's lazy evaluation rules:
rm(x)
rm(y)
## neither x nor y is defined
(x <- y) <- 10
## Error in (x <- y) <- 10 : object 'x' not found
## both x and y are defined
x <- y <- 5
(x <- y) <- 10
## Error in (x <- y) <- 10 : could not find function "(<-"