|
R is such a weird little language. It's basically lazy Lisp dressed up in C syntax. For example, the only operator it really has is a function call. Everything else is syntactic sugar for a function call, and I mean literally everything: assignments, conditionals, loops, even function definitions and curly braces are all function calls. For example: a <- 1;
# is the same as
`<-`(a, 1);
if (a == 1) print("ok"); else { print("wtf"); print(a); }
# is the same as
`if`(a == 1, print("ok"), `{`(print("wtf"), print(a)));
function(x, y=42) x + y;
# is the same as
`function`(pairlist(x=, y=42), x + y); # not quite but close enough
and so on. You can actually see what things look like under the hood for any R expression or statement by printing as.list(quote(...)), and recursively doing that for every element in the resulting listThe reason why this is possible is because all arguments in R are not evaluated when passed to a function. Instead, it receives the expression object corresponding to the expression that the caller used for that argument, combined with the environment in which it was created - R calls this a promise. It's kind of like instead of (foo (+ x y)), you'd write: (foo (`(+ x y) (lambda () (+ x y)))
i.e. for the argument, instead of its evaluated value, you passed both the quoted expression and the lambda that computes it in the original environment. When the actual value of the argument is needed, the expression is evaluated and the result is cached in the promise (so implicit eval is lazy and one-off). But the function can instead just query for the argument expression directly and then use it in some other way - so e.g. the `<-` function does not eval its first argument, but instead uses it to identify the variable being set. |