Há um comportamento estranho de lobstr::obj_addr causado por sua vetorização sobre listas, quando a própria lista não altera o endereço
Acabei de começar Advanced R de Wickham (2ª ed.) e cheguei ao primeiro exercício 2.2.2 Exercises. Eu supus que, dado:
a <- 1:10; b <- a; c <- b
todos eles teriam o mesmo endereço de memória recuperado pela lobstr::obj_addr
função. Isso é verdade se usarmos apenas a, b ou c como entradas, mas como sou preguiçoso e queria ter todos os valores de uma vez, fiz:
list(a, b, c) |> lapply(obj_addr) # lapply or sapply
Então obtemos um conjunto diferente de valores entre os diferentes nomes toda vez que a função é executada. Isso ainda acontece se definimos x <- list(a, b, c)
antes de chamar a função por meio de lapply
, e obj_addr(x[[1]]) == obj_addr(x[[2]]) == obj_addr(x[[3]]) == obj_addr(a)
, então não é uma questão de criar uma nova lista toda vez. Alguém sabe o que está acontecendo aqui? Eu entendo que até certo ponto cada chamada gera um novo objeto de saída que terá seu próprio endereço de memória, mas não sei como lapply
pode interferir em uma função constante para um determinado objeto como obj_addr
.
Agradeço antecipadamente!
Isso é causado por um bug na forma como
lobstr
identifica o ambienteMrFlick apontou nos comentários que
x |> lapply(function(x) obj_addr(x))
retorna a resposta certa ex |> lapply(obj_addr)
retorna a errada. Isso indica que algo estranho está acontecendo com os ambientes. A questão é se isso é causado porlobstr::obj_addr()
oulapply()
. Acontece que isso decorre da maneira comolobstr
usarlang
, o que faz com que ele procure o objeto no ambiente incorreto.O que faz
lobstr::obj_addr()
?A fonte (ligeiramente simplificada) é a seguinte:
obj_addr_()
é a função workhorse escrita em C que obtém o endereço de memória. No entanto, primeiro o Robj_addr()
usa asrlang
ferramentas quosure como uma forma de acessar o objeto sem aumentar a contagem de referências . Isso permite que a coleta de lixo aconteça corretamente mais tarde, garantindo que não haja referências ao objeto por aí. No entanto, no caso delapply(x, lobstr::obj_addr)
, ele não acessa corretamente o ambiente dos elementos dex
, que é onde o erro surge.O que acontece com os ambientes em
lapply()
?Considere a seguinte função para obter o ambiente de um objeto do qual uma função é chamada:
Podemos chamar isso de
lapply()
ambas as maneiras:O primeiro conjunto de resultados faz sentido.
lapply()
cria um ambiente temporário cada vez que chama a função anônima (o fechamento da função).No entanto, não faz sentido que
lapply(x, f)
esteja sendo executado no ambiente vazio. Sabemos que podemos nos referir a objetos no ambiente global comlapply()
. Mas o ambiente vazio por definição não contém objetos e não tem pai:Então
rlang::quo_get_env(rlang::enquo(x))
claramente retorna o ambiente errado. Vamos tentar encontrar o ambiente pai da função chamada usando olapply()
R base:Isso faz mais sentido e nos dá uma pista sobre o que está acontecendo.
Escrevendo nossa própria função para obter o ponteiro
Para descartar
lapply()
como fonte dessa inconsistência, vamos escrever nossa própria versão delobstr::obj_addr()
que não mexe com ambientes. A linha relevante daobj_addr_()
função de nível C é onde ela converte oSEXP
para um ponteiro:Aqui está uma função semelhante para obter o ponteiro que ignora o
rlang
conteúdo:Comparando
get_pointer()
comlobstr::obj_addr()
Vamos definir
x
e verificar os endereços individualmente:Agora podemos comparar os resultados usando
lobstr
de três maneiras. Usareisapply()
em vez delapply()
pois imprime melhor. Podemos ver quesapply(x, lobstr::obj_addr)
não está correto.A questão é se podemos obter os resultados corretos se pularmos as coisas do ambiente. É aqui que podemos usar
get_pointer()
:Então
get_pointer()
obtém os resultados corretos nas duas vezes. Isso indica que o problema está nolobstr
uso de ferramentas de quosure do rlang. Na verdade, não tenho certeza se isso é umrlang
problema ou se o problema é comolobstr
está usandorlang
. No entanto, como ambos os pacotes são parte do r-lib , imagino que um relatório de bug arquivado em qualquer um deles encontraria seu caminho para o lugar certo bem rápido.Parece que esse comportamento se deve ao fato de que
obj_addr
estáenquo
ing seu argumento antes de recuperar seu endereço (veja a definição da função). Então, podemos examinar o comportamento deenquo
separadamente da parte real de recuperação de endereço.Para verificação, podemos definir uma função baseada em
obj_addr
que recupera o endereço como:Para referência, o objeto
a
está localizado em:Quando fora do corpo de um fechamento,
enquo
retorna algo inútil para recuperar o endereço do objeto, poisenquo
parece ser capaz de avaliar um símbolo completamente (em contraste com,substitute
por exemplo) e retornar um objeto recém-construído:Dentro de um fechamento, porém, tudo funciona como esperado:
lapply
avalia seus argumentos durante a construção da chamada e, provavelmente, podemos simular seu comportamento comforce
(para deixar claro) assim:já que
enquo
não retorna mais um símbolo pesquisável.Como MrFlick observa nos comentários, envolver com outra camada de fechamento funciona como esperado, pois
enquo
não parece chegar a uma avaliação completa de seu argumento:Além disso, por exemplo, se redefinirmos
obj_addr
ao longo das linhas de:então
lapply
não causa confusão: