While writing a toy version of the +
operator that only adds the furthest left and right hand sides of the sum, I came across this situation where {{}}
functions as I expected and !!rlang::enquo()
signals an error.
On the Embrace Operator reference page it says that under the hood "{{
combines enquo()
and !!
in one step". So in this case, why don't {{}}
and !!rlang::enquo()
function the same way?
library(rlang)
# Outside-only sum using `{{}}`
`%+%` <- function(lhs, rhs) {
# Quote the `lhs` to inspect
lhs_expr <- rlang::quo_get_expr(rlang::enquo(lhs))
lhs_call <- lhs_expr[[1]]
# Base Case
if (lhs_call != as_name("%+%")) {
lhs <- eval(lhs) # Evaluate `lhs` as it may still be a call
return(lhs + rhs)
}
# Repeat using the left-hand-side of the next `%+%` as the new `lhs`
lhs_first_arg <- lhs_expr[[2]]
{{ lhs_first_arg }} %+% rhs
}
1 %+% 1
#> [1] 2
1 %+% 2 %+% 3 %+% 4 # 5
#> [1] 5
mean(c(1, 2, 3)) %+% "A" %+% "B" %+% -10
#> [1] -8
# Outside-only sum using `!!enquo()`
`%+%` <- function(lhs, rhs) {
# Quote the `lhs` to inspect
lhs_expr <- rlang::quo_get_expr(rlang::enquo(lhs))
lhs_call <- lhs_expr[[1]]
# Base Case
if (lhs_call != as_name("%+%")) {
lhs <- eval(lhs) # Evaluate `lhs` as it may still be a call
return(lhs + rhs)
}
# Repeat using the left-hand-side of the next `%+%` as the new `lhs`
lhs_first_arg <- lhs_expr[[2]]
(!!enquo(lhs_first_arg)) %+% rhs
}
1 %+% 1
#> [1] 2
try(1 %+% 2 %+% 3 %+% 4)
#> Error in eval(lhs) :
#> Quosures can only be unquoted within a quasiquotation context.
#>
#> # Bad: list(!!myquosure)
#>
#> # Good: dplyr::mutate(data, !!myquosure)
Created on 2023-11-15 with reprex v2.0.2
P.S. Another version which doesn't use enquo()
in the recursive case also works fine - so does {{}}
pass through bare expressions and !!enquo()
not?
library(rlang)
# Outside-only sum, not quoting `lhs` in the recursive case
`%+%` <- function(lhs, rhs, fn = rlang::caller_fn()) {
# Quote the `lhs` to inspect
if (!identical(fn, `%+%`)) {
lhs_expr <- rlang::quo_get_expr(rlang::enquo(lhs))
} else {
lhs_expr <- lhs
}
lhs_call <- lhs_expr[[1]]
# Base Case
if (lhs_call != as_name("%+%")) {
lhs <- eval(lhs) # Evaluate `lhs` as it may still be a call
return(lhs + rhs)
}
# Repeat using the left-hand-side of the next `%+%` as the new `lhs`
lhs_first_arg <- lhs_expr[[2]]
lhs_first_arg %+% rhs
}
1 %+% 1
#> [1] 2
1 %+% 2 %+% 3 %+% 4 # 5
#> [1] 5
mean(c(1, 2, 3)) %+% "A" %+% "B" %+% -10
#> [1] -8
Created on 2023-11-15 with reprex v2.0.2