Funções de substituição, como names<-
, parecem não usar avaliação preguiçosa quando chamadas como names(x) <- c("a", "b")
.
Para demonstrar, vamos definir uma função para obter a parte fracionária de um número e uma função de substituição correspondente - mas dentro da função de substituição, inclua uma linha para imprimir o value
argumento neutralizado.
fractional <- function(x) {
x %% 1
}
`fractional<-` <- function(x, value) {
print(rlang::enexpr(value))
invisible(x %/% 1 + value)
}
Agora, se chamarmos fractional<-
diretamente, ele imprime a expressão que demos para value
:
x <- 10.1
`fractional<-`(x, 0.2 + 0.2)
#> 0.2 + 0.2
Mas se o chamarmos no formulário de atribuição, ele imprime o resultado da avaliação da expressão:
x <- 10.1
fractional(x) <- 0.2 + 0.2
#> [1] 0.4
A definição da linguagem explica funções de substituição como:
names(x) <- c("a","b")
é equivalente a
`*tmp*` <- x x <- "names<-"(`*tmp*`, value=c("a","b")) rm(`*tmp*`)
Mas isso não reproduz esse comportamento:
x <- 10.1
`*tmp*` <- x
x <- "fractional<-"(`*tmp*`, value=0.2 + 0.2)
rm(`*tmp*`)
#> 0.2 + 0.2
O que está acontecendo internamente <-
que faz com que value
seja passado para fractional<-
depois de ser avaliado, e há alguma maneira de contornar esse comportamento?
Edição: @SamR destacou que usar substitute
captura a expressão da promessa:
x <- 10.1
`fractional<-` <- function(x, value) {
print(substitute(value))
invisible(x %/% 1 + value)
}
fractional(x) <- 0.2 + 0.2
#> 0.2 + 0.2
Então, claramente eu estava enganado ao assumir que value
estava sendo avaliado antes de ser passado para fractional<-
. No entanto, eu ainda gostaria muito de saber por que base::substitute
funciona como esperado aqui enquanto rlang::enexpr
e amigos não . Afinal, enexpr
usa substitute
internamente:
enexpr <- function(arg) {
.Call(ffi_enexpr, substitute(arg), parent.frame())
}
A depuração no R Studio mostra que, tanto quando chamado na forma de atribuição como fractional(x) <- 0.2 + 0.2
quanto na forma de prefixo "fractional<-"(x, 0.2 + 0.2)
, fractional<-
é passada uma promessa não avaliada para value
:
Isso permanece não avaliado quando chamado na forma de prefixo:
Mas é avaliado após a chamada para enexpr
quando chamado no formulário de atribuição:
Estou pensando se isso tem a ver com o fato de que no formulário de atribuição, a função é chamada por uma função primitiva, <-
? Mas não está claro por que isso faria diferença.