Reputation: 1057
The Chapter 19 of Advanced R explains that expr()
is not useful inside the function.
However, in the following case, I could not make a function work without expr()
.
Let's suppose I want to group a tibble in a function.
data(iris)
iris %>% group_by(Species)
The obvious approach is to use "curly curly".
func_a <- function(data, grouping) {
data %>% group_by({{grouping}})
}
func_a(iris, Species)
However, "curly curly" does not work if I allow an arbitrarily supplied expressions.
func_b <- function(data, ...) {
data %>% group_by({{...}})
}
func_b(iris, Species)
# Error in (function (x) : object 'Species' not found
In the end, I found I need to expr()
to make it work.
func_c <- function(data, ...) {
grouping <- expr(...)
data %>% group_by(!!grouping)
}
func_c(iris, Species)
The example of expr()
in Advanced R is:
f1 <- function(x) expr(x)
f1(a + b + c)
#> x
My main question is why func_c
works. Does expr()
take ...
as it is and evaluate it with !!
? Why we have to take a different approach for ...
?
Then, I am not sure why this does not work.
func_d <- function(data, grouping) {
grouping <- expr(grouping)
data %>% group_by(!!grouping)
}
func_d(iris, Species)
I also checked rlang manual, but the explanation is too brief for me.
Upvotes: 2
Views: 1086
Reputation: 13691
Think of it as expr()
and !!
effectively negating each other.
func_c <- function(data, ...) {
grouping <- expr(...)
data %>% group_by(!!grouping)
}
is equivalent to
func_c <- function(data, ...) {
data %>% group_by(...) # Proper way to handle dots, by the way
}
(It's not an exact equivalence, because the former implementation expands dots in expr
, while the latter does it in group_by
. But both implementations will produce the same output when a single column symbol is supplied to ...
.)
Likewise,
func_d <- function(data, grouping) {
grouping <- expr(grouping)
data %>% group_by(!!grouping)
}
is equivalent to
func_d <- function(data, grouping) {
data %>% group_by(grouping) # No column `grouping` in iris
}
To get func_d
working, you need to replace expr()
with enexpr()
. This will capture the expression provided to the function, as opposed to the expression grouping
itself.
Upvotes: 0
Reputation: 1007
expr
prevents the evaluation of code. For instance, attempting to run x
by itself will fail unless x
is a variable you've previously declared - R will go looking for the value in x
when you evaluate it, and will issue an error if no such value is found. In contrast, expr(x)
will never fail (even if x
hasn't been declared yet), because the expr
tells R "take this at face value and don't go looking for something else it might represent". expr(x)
will return something of the type "name", which is basically just that - a name. You can think of a name as the interface between you and R - it's what you type in, and how you communicate your instructions. eval(expr(x))
is the same as just doing x
.
Now taking your examples in order:
func_c <- function(data, ...) {
grouping <- expr(...)
data %>% group_by(!!grouping)
}
func_c(iris, Species)
This works because Species
will be directly passed to expr
through the ...
, and the return type will be a "name", which gets stored in the variable grouping
. A name can be evaluated with !!
as you did it, or with eval
. Either way, doing !!grouping
will cause R to first go looking for what the grouping
variable represents, finding Species
. The variable gets replaced with its value, and finally !!Species
will tell R to go looking for the variable called Species, which within the context of the group_by
function will give you the column called "Species".
Moving on to your next example:
f1 <- function(x) expr(x)
f1(a + b + c)
This doesn't work simply because expr(x)
blocks any evaluation. R doesn't go looking for what's inside x
, so it never finds a + b + c
, it takes the x
at face value, and that's what you get.
Finally, we have your last example:
func_d <- function(data, grouping) {
grouping <- expr(grouping)
data %>% group_by(!!grouping)
}
func_d(iris, Species)
This is similar to your first example, but there's an extra variable at play here - the parameter called grouping
. In your first example, Species
entered the function directly (through ...
), so it wasn't bound to any parameter name. In this third example, Species
enters the function through a named parameter, i.e. bound to the variable grouping
. However, expr(grouping)
tells R "don't bother looking for what grouping
represents, I have everything I need right here"... so it never finds Species
at all. expr(grouping)
just gives you the name grouping
, regardless of whatever's in the variable itself. So then when you try to evaluate that using !!grouping
within group_by
, R tries to look for a column name called grouping
... Needless to say, it doesn't find it, and you get a Column grouping is not found
error.
Upvotes: 2