I struggle with quosures on something that must have a trivial solution.
I have a function that has an argument, below called a, that may take a vector or a (non-quoted) column name.
Such argument work flawlessly within mutate() calls, but in my case I would like to directly use the argument to build a tibble, so sometimes the argument must be read as a quosure to be evaluated in the initial table, and othertimes it should be evaluated directly. OK, I struggle to explain but the example below should help:
foo <- function(table, a) {
tibble(a = {{a}})
}
tibble(b = 1) %>% foo(a = 1)
tibble(b = 1) %>% foo(a = b) ## does not work... I would like it to give the same output as the line above...
How can I thus modify the code below so that it works as expected?
Thanks!
I am not sure this is a best way but it works like this
library(tibble)
library(magrittr)
foo <- function(table, a) {
a <- rlang::enquo(a)
tibble(a = rlang::eval_tidy(a, table))
}
tibble(b = 2) %>%
foo(a = 1)
#> # A tibble: 1 x 1
#> a
#> <dbl>
#> 1 1
tibble(b = 2) %>%
foo(a = b)
#> # A tibble: 1 x 1
#> a
#> <dbl>
#> 1 2
c <- 4
tibble(b = 2) %>%
foo(a = c)
#> # A tibble: 1 x 1
#> a
#> <dbl>
#> 1 4
Created on 2019-07-02 by the reprex package (v0.3.0.9000)
You need to use rlang functions to manage something like that. The new {{}} syntax won't help here because you need something more than just !!enquo(a)
eval_tidy allows to evaluate using tidyeval with some data as mask to the environements. I let you read the help page to learn a bit more about data masks.
Why would a be 1 in the output? If I understand it correctly, eval_tidy should use the data mask build from table to override the environment defined in the quosure before evaluating a. Thus to me, a should be 2 and not 1. Obviously I am wrong, but then what's happening... Could you please clarify? I did read both help files ?eval_tidy and ?as_data_mask but perhaps I missed something.
Yes but it does not mask the function environment. here, a is defined inside the function so it is found before the data mask and before the global environment.
library(tibble)
library(magrittr)
foo <- function(table, a) {
a <- rlang::enquo(a)
a <- 3
tibble(a = rlang::eval_tidy(a, table))
}
tibble(a = 2) %>%
foo(a = 1)
#> # A tibble: 1 x 1
#> a
#> <dbl>
#> 1 3
Created on 2019-07-03 by the reprex package (v0.3.0.9000)
That is why you get 1 because it is define inside the function as a <- rlang::enquo(a).
I think it is: function env > data mask > parent env
I don't know what you are trying to achieve but playing with names, symbols and environment is not something easy and can be the source of some mistake. one should go with caution.
Thanks @cderv, the goal I am trying to achieve is to understand how the tidyverse really works and how to program tidy functions not by trials and errors. From the R documentation, I thought that both tibble() and mutate() were quoting functions that would behave in the same way. As you showed me, it turns out they behave quite differently as tibble() requires an explicit call to eval_tidy(). Now, I gather that the key for a better understanding is probably to grasp how the scoping really works during a tidy evaluation. I know it is difficult. I will keep digging.
@hadley, pardon me if I am wrong but I think you missed something: your are passing x to table and nothing to a, so I think that the error message is not particularly surprising, unless you think it should specify that a is missing. Please clarify and I will file the issue if needed.
Best,
Alex