Home > other >  How to evaluate a glue expression in a nested function?
How to evaluate a glue expression in a nested function?

Time:10-30

I'm trying to nest a function that glues together two strings within a function that uses the combined string to name a column of a dataframe. However, the problem seems to be that the glue expression is not evaluated to a string early enough. Can (and should) I force the expression to be evaluated before it is being passed on as an argument to another function?

library(tidyverse)

# define inner function
add_prefix <- function(string) {
  x <- glue::glue("prefix_{string}")
  return(as.character(x))
}

# define outer function
mod_mtcars <- function(df, name) {
  df %>% 
    mutate({{ name }} := mpg ^ 2)
}

mod_mtcars(mtcars, add_prefix("foo"))
#> Error: The LHS of `:=` must be a string or a symbol

# alternative outer function with explicit enquoting
mod_mtcars2 <- function(df, name) {
  name <- ensym(name)
  
  df %>% 
    mutate(!!name := mpg ^ 2)
}

mod_mtcars2(mtcars, add_prefix("foo"))
#> Error: Only strings can be converted to symbols

Created on 2021-10-30 by the reprex package (v2.0.1)

CodePudding user response:

name is not a symbol. Try:

mod_mtcars <- function(df, name) {
  name <- sym(name)
  df %>%
    mutate({{ name }} := mpg ^ 2)
}

mod_mtcars(mtcars, add_prefix("foo"))

Even better, tidy eval now supports glue strings so you could simplify with:

# don't need add_prefix()
mod_mtcars <- function(df, name){
  df %>% 
    mutate("prefix_{{name}}" := mpg ^ 2)
}

mod_mtcars(mtcars, foo)

Lastly, the curly-curly operator tunnels your expression and you're passing it a string. If you keep your current functions use the bang-bang operator instead:

mod_mtcars <- function(df, name){
  df %>% 
    mutate(!! name := mpg ^ 2)
}

mod_mtcars(mtcars, add_prefix("foo"))

CodePudding user response:

We may need to escape the input (keeping the OP's original function unchanged)

mod_mtcars(mtcars, !!add_prefix("foo"))

-output

                   mpg cyl  disp  hp drat    wt  qsec vs am gear carb prefix_foo
Mazda RX4           21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4     441.00
Mazda RX4 Wag       21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4     441.00
Datsun 710          22.8   4 108.0  93 3.85 2.320 18.61  1  1    4    1     519.84
Hornet 4 Drive      21.4   6 258.0 110 3.08 3.215 19.44  1  0    3    1     457.96
Hornet Sportabout   18.7   8 360.0 175 3.15 3.440 17.02  0  0    3    2     349.69
Valiant             18.1   6 225.0 105 2.76 3.460 20.22  1  0    3    1     327.61
Duster 360          14.3   8 360.0 245 3.21 3.570 15.84  0  0    3    4     204.49
...

CodePudding user response:

First of all, the glue string syntax is now preferred over embracing directly in the LHS. So prefer "{{ var }}" := expr to {{ var }} := expr. In a future version of rlang (next year) we'll make it possible to use glue strings with =. At that point, := will be pretty much superseded. We went with := to allow !! injection on the LHS before glue support was added.

Second, your problem is that you're using {{ instead of simple injection. {{ is for injecting the expression supplied as argument, not the value of the expression. Use normal glue interpolation with "{" to inject the value instead:

mod_mtcars <- function(df, name) {
  df %>% 
    mutate("{name}" := mpg ^ 2)
}

PS: Your !! version had a similar problem. Because you used ensym() on the argument, you were defusing the expression supplied as argument instead of using the value. But ensym() requires the expression to be a simple name and you supplied a full computation, causing an error. You can fix it like this:

mod_mtcars2 <- function(df, name) {
  df %>%
    mutate(!!name := mpg ^ 2)
}

But glue syntax is now preferred.

  • Related