3

I've partialised the glue function in a project I'm working on so that we can use agreed-on delimiters without having to tell glue about them all the time. But when we go to use the partialised function inside another function, it stops working:

library(purrr)
library(glue)

glue_query <- partial(glue, .open = "<<", .close = ">>")

# case 1: use partialised function in same scope
x <- 15
glue_query("There are <<x>> apples here!")
#> There are 15 apples here!

# case 2: use partialised function in different scope
myfunc_partialised <- function(y) {
  glue_query("The thing I called y is actually <<y>>")
}
myfunc_partialised(15)
#> Error in eval(parse(text = text, keep.source = FALSE), envir): object 'y' not found

# case 3: use function directly
myfunc_regular <- function(z) {
  glue("The thing I called z is actually <<z>>", .open = "<<", .close = ">>")
}
myfunc_regular(15)
#> The thing I called z is actually 15

Created on 2021-07-09 by the reprex package (v2.0.0)

I get the sense that glue_query is looking for objects to interpolate in the environment it was defined in, rather than in the environment it's called in. Is that's what happening here? Can I direct it to use the calling environment? I'd like to use it throughout my package!

EDIT: I understand that glue has an .envir argument that controls which environment the expressions are evaluated in, but I'm not quite sure what to use to ensure it plays nice here!

jimjamslam
  • 1,988
  • 1
  • 18
  • 32

2 Answers2

1

It does seem that partial() makes it much more difficult to get the correct environment. Instead you can write your own wrapper

glue_query <- function(..., .open = "<<", .close = ">>", .envir=parent.frame()) {
  glue(..., .open=.open, .close=.close, .envir=.envir)
}

This will work with both the test cases you provided.

MrFlick
  • 195,160
  • 17
  • 277
  • 295
1

One alternative is to change the defaults of glue, define your function, the reset the defaults:

library(glue)
library(default)

default(glue) <- list(.open = "<<", .close = ">>")
glue2 <- glue
reset_default(glue)
#> function (..., .sep = "", .envir = parent.frame(), .open = "{", 
#>     .close = "}", .na = "NA", .transformer = identity_transformer, 
#>     .trim = TRUE) 
#> {
#>     glue_data(.x = NULL, ..., .sep = .sep, .envir = .envir, .open = .open, 
#>         .close = .close, .na = .na, .transformer = .transformer, 
#>         .trim = .trim)
#> }
#> <bytecode: 0x0000000014c59d80>
#> <environment: namespace:glue>

# case 1: use partialised function in same scope
x <- 15
glue_query("There are <<x>> apples here!")
#> Error in glue_query("There are <<x>> apples here!"): could not find function "glue_query"
#> There are 15 apples here!

# case 2: use partialised function in different scope
myfunc_partialised <- function(y) {
  glue2("The thing I called y is actually <<y>>")
}
myfunc_partialised(15)
#> The thing I called y is actually 15

# case 3: use function directly
myfunc_regular <- function(z) {
  glue("The thing I called z is actually <<z>>", .open = "<<", .close = ">>")
}
myfunc_regular(15)
#> The thing I called z is actually 15

Created on 2021-07-09 by the reprex package (v0.3.0)

Hugh
  • 15,521
  • 12
  • 57
  • 100
  • Thanks @Hugh! I'm leaning toward MrFlick's solution, since I think it's about the same complexity without introducing more dependencies, but they're both great solutions :) – jimjamslam Jul 09 '21 at 03:34