tidyverse/glue

`glue` does not inherit calling environment when value is defined in different environment

Closed this issue · 2 comments

klmr commented

Reprex:

library(glue)

e <- list2env(list(a = 1, x = "`a` has the value: {a}"))

# Works:
local(glue(x), envir = e)
# Fails:
local(lapply(x, glue), envir = e)

This fails with the error

Error in eval(parse(text = text, keep.source = FALSE), envir) : 
  object 'a' not found

This can be fixed by passing .envir = e to lapply or by replacing lapply(x, glue) with lapply(x, \(x) glue(x)) but this should be unnecessary: after all, the entire lapply call is evaluated locally in e, and the function inside it should be able to look up a in the chain of parent frames.

This behaviour also causes lapply(x, glue) to fail in a RMarkdown document.

For more discussion see https://stackoverflow.com/q/75107761/1968.

The fact that this works:

library(glue)

e <- list2env(list(a = 1, x = "`a` has the value: {a}"))
local(lapply(x, \(x) glue(x)), envir = e)
#> [[1]]
#> `a` has the value: 1

Created on 2023-01-25 with reprex v2.0.2

Makes me think it's due to the specifics of how lapply() works. I'll try and remember the detail, but I'm pretty sure this is a useful diagnostic.

klmr commented

Yes, of course you’re right. In fact, plain get has the same issue:

local(get('a'), e)
# [1] 1
local(lapply('a', get), e)
# Error in FUN(X[[i]], ...) : object 'a' not found

And now that I think about it, this makes sense: lapply’s local environment obviously does not contain this value, and the chain of parent environments being searched go to the environment defining the function, not to the one calling it.

The fact that this works at all with global variables is due to the oddity of parent.env(.BaseNamespaceEnv) being .GlobalEnv.

And it’s not just lapply either, purrr::map* exhibits the same behaviour (see linked discussion), as would any obvious implementation of such a function.