9

I can see from the documentation that rlang::enquo() and rlang::quo() are used in different contexts. Hence, I used rlang::enysm() recently within a function declaration (see below). However, wrapped inside another SE function call, I got an unexpected error which I guess is related to lazy evaluation (and which goes away if I force(x) in f_enysm()). But it seems as I can also work around that by simply using sym(x) instead of ensym(x) since x is a string that does not convey any information about environments (as opposed to quosures).

Is that safe?

If yes, I don't see when I should prefer ensym() over sym and the proposed use seems inconsistent with the terminology used with quo() / enquo(), expr() / enexpr() etc.

library(rlang)
f_ensym <- function(data, x, fun) {
  x <- fun(x)
  head(dplyr::arrange(data, !!x))
}
f_ensym(mtcars, "cyl", sym)
#>    mpg cyl  disp hp drat    wt  qsec vs am gear carb
#> 1 22.8   4 108.0 93 3.85 2.320 18.61  1  1    4    1
#> 2 24.4   4 146.7 62 3.69 3.190 20.00  1  0    4    2
#> 3 22.8   4 140.8 95 3.92 3.150 22.90  1  0    4    2
#> 4 32.4   4  78.7 66 4.08 2.200 19.47  1  1    4    1
#> 5 30.4   4  75.7 52 4.93 1.615 18.52  1  1    4    2
#> 6 33.9   4  71.1 65 4.22 1.835 19.90  1  1    4    1


f_sym <- function(data, x) {
  x <- sym(x)
  head(dplyr::arrange(data, !!x))
}

g <- function(data, x, fun) {
  fun(data, x)
}

g(mtcars, "cyl", f_ensym)
#> Error in fun(x): argument "fun" is missing, with no default
g(mtcars, "cyl", f_sym)
#>    mpg cyl  disp hp drat    wt  qsec vs am gear carb
#> 1 22.8   4 108.0 93 3.85 2.320 18.61  1  1    4    1
#> 2 24.4   4 146.7 62 3.69 3.190 20.00  1  0    4    2
#> 3 22.8   4 140.8 95 3.92 3.150 22.90  1  0    4    2
#> 4 32.4   4  78.7 66 4.08 2.200 19.47  1  1    4    1
#> 5 30.4   4  75.7 52 4.93 1.615 18.52  1  1    4    2
#> 6 33.9   4  71.1 65 4.22 1.835 19.90  1  1    4    1

# If I remove one level, I don't get the problematic behaviour.
f <- function(data, x, fun) {
  x <- fun(x)
  head(dplyr::arrange(data, !!x))
}
f(mtcars, "cyl", sym)
#>    mpg cyl  disp hp drat    wt  qsec vs am gear carb
#> 1 22.8   4 108.0 93 3.85 2.320 18.61  1  1    4    1
#> 2 24.4   4 146.7 62 3.69 3.190 20.00  1  0    4    2
#> 3 22.8   4 140.8 95 3.92 3.150 22.90  1  0    4    2
#> 4 32.4   4  78.7 66 4.08 2.200 19.47  1  1    4    1
#> 5 30.4   4  75.7 52 4.93 1.615 18.52  1  1    4    2
#> 6 33.9   4  71.1 65 4.22 1.835 19.90  1  1    4    1
f(mtcars, "cyl", ensym)
#>    mpg cyl  disp hp drat    wt  qsec vs am gear carb
#> 1 22.8   4 108.0 93 3.85 2.320 18.61  1  1    4    1
#> 2 24.4   4 146.7 62 3.69 3.190 20.00  1  0    4    2
#> 3 22.8   4 140.8 95 3.92 3.150 22.90  1  0    4    2
#> 4 32.4   4  78.7 66 4.08 2.200 19.47  1  1    4    1
#> 5 30.4   4  75.7 52 4.93 1.615 18.52  1  1    4    2
#> 6 33.9   4  71.1 65 4.22 1.835 19.90  1  1    4    1

Also, if I remove the intermediate function f_sym() and f_enysm() and make a direct call to f(), I don't get the probelmatic behaviour.

f <- function(data, x, fun) {
  x <- fun(x)
  head(dplyr::arrange(data, !!x))
}
f(mtcars, "cyl", sym)
#>    mpg cyl  disp hp drat    wt  qsec vs am gear carb
#> 1 22.8   4 108.0 93 3.85 2.320 18.61  1  1    4    1
#> 2 24.4   4 146.7 62 3.69 3.190 20.00  1  0    4    2
#> 3 22.8   4 140.8 95 3.92 3.150 22.90  1  0    4    2
#> 4 32.4   4  78.7 66 4.08 2.200 19.47  1  1    4    1
#> 5 30.4   4  75.7 52 4.93 1.615 18.52  1  1    4    2
#> 6 33.9   4  71.1 65 4.22 1.835 19.90  1  1    4    1
f(mtcars, "cyl", ensym)
#>    mpg cyl  disp hp drat    wt  qsec vs am gear carb
#> 1 22.8   4 108.0 93 3.85 2.320 18.61  1  1    4    1
#> 2 24.4   4 146.7 62 3.69 3.190 20.00  1  0    4    2
#> 3 22.8   4 140.8 95 3.92 3.150 22.90  1  0    4    2
#> 4 32.4   4  78.7 66 4.08 2.200 19.47  1  1    4    1
#> 5 30.4   4  75.7 52 4.93 1.615 18.52  1  1    4    2
#> 6 33.9   4  71.1 65 4.22 1.835 19.90  1  1    4    1
Jaap
  • 81,064
  • 34
  • 182
  • 193
Lorenz Walthert
  • 4,414
  • 1
  • 18
  • 24
  • Just saw your updated example. If you look at the function, when you are running `g` the `fun` for it is `f_ensym` and as I can find in your arguments, there is no where 'sym' or `ensym` is being taken as input for it to process – akrun Mar 10 '18 at 12:40
  • 1
    `ensym()` allows the user of your function to supply non-quoted names. In addition it accepts strings because it's meant to mimic the syntax of arguments where you can supply both in the LHS, e.g. `list(bare = 1, "quoted" = 2)` – Lionel Henry Mar 10 '18 at 13:29
  • @lionel I just wonder how you evaluate the `g(mtcars, "cyl", f_ensym, ensym)` (below solution) as the the `fun(x)` returns `x` instead of `cyl` inside the function. I tried the `!!`, If you have a better solution, please post it – akrun Mar 10 '18 at 13:44
  • You'd need to unquote with `!!`. I wonder what's the application for this higher-order function, it seems a bit convoluted. – Lionel Henry Mar 11 '18 at 18:57

1 Answers1

7

The ensym can take both quoted and unquoted arguments

f_ensym(mtcars, "cyl")
f_ensym(mtcars, cyl)

Based on the updated example in the OP's post, the while the sym takes the string object g is only taking three argument and the fun part of it is 'f_ensymwhich also have afun` that is not being passed into. We can have one more argument for that

g <- function(data, x, fun, fun2) {
     fun(data, x, fun2)
  }

g(mtcars, "cyl", f_ensym, sym)
#   mpg cyl  disp hp drat    wt  qsec vs am gear carb
#1 22.8   4 108.0 93 3.85 2.320 18.61  1  1    4    1
#2 24.4   4 146.7 62 3.69 3.190 20.00  1  0    4    2
#3 22.8   4 140.8 95 3.92 3.150 22.90  1  0    4    2
#4 32.4   4  78.7 66 4.08 2.200 19.47  1  1    4    1
#5 30.4   4  75.7 52 4.93 1.615 18.52  1  1    4    2
#6 33.9   4  71.1 65 4.22 1.835 19.90  1  1    4    1
akrun
  • 874,273
  • 37
  • 540
  • 662
  • Ok thanks, so I assume it's safer to use `sym()` within function declarations if I want to avoid quoting in any case and know the input is a string or a variable pointing to a string, right? – Lorenz Walthert Mar 10 '18 at 11:36
  • @LorenzWalthert If the input is always a string, then `sym` can be used – akrun Mar 10 '18 at 11:37