Alguém poderia me explicar por que usar uma chamada de função como argumento padrão falha quando o nome da função é o mesmo que o nome do argumento?
f1 <- function(){
2
}
f2 <- function(f1 = f1()){
f1 * 2
}
f2()
#> Error in f2(): promise already under evaluation: recursive default argument reference or earlier problems?
Entendo que a função pode ser feita funcionar (1) renomeando a função f1
ou (2) renomeando o argumento para f2
(veja abaixo), mas não consigo entender por que essas correções funcionam, enquanto o caso com nomes alinhados falha. Também entendo que muitos considerariam esse tipo de nomenclatura inadequada, mas, para os propósitos desta pergunta, estou interessado nos processos subjacentes a esse erro, não na deficiência de estilo associada.
Correção 1
f <- function(){
2
}
f2 <- function(f1 = f()){
f1 * 2
}
f2()
#> [1] 4
Correção 2
f1 <- function(){
2
}
f2 <- function(.f1 = f1()){
.f1 * 2
}
f2()
#> [1] 4
Agradeço desde já a sua ajuda.
Primeiramente: isso se deve à forma como os namespaces são pesquisados por herança. Uma boa referência é https://adv-r.hadley.nz/environments.html .
Isso é semelhante a como um argumento está disponível para outros argumentos:
Sequência de avaliações:
b+2
desencadeia a avaliação deb
, um argumento formalb=a+1
é avaliado, inclui um símbolo, então ...a
está no mesmo ambiente, então ele avaliaa
e1
passa de volta para a cadeiad
nunca é usado, portanto nunca é avaliadoO seu problema é que a herança de objetos (quadro para quadro pai, etc., o caminho de busca para variáveis e escopo dinâmico) para no momento em que encontra algo. Ela procura no ambiente atual, no quadro de chamada/pai e assim por diante até chegar
global()
a um beco sem saída (nem todos os caminhos terminam emglobalenv()
).Avaliação da sua
f1
função:f1 = f1()
encontraf1
no ambiente atual, independentemente do fato de ser auto-referencial e ainda uma "promessa" em avaliação (não a mesma quepromises
a promessa de); uma vez que é encontrado, ele para de pesquisar e avalia aquele, o que desencadeia uma avaliação recursivaEm nenhum momento R dirá "bem, isso
f1
não está funcionando para mim, vamos continuar procurando mais no caminho de busca".Para ser justo, um exemplo artificial
pode sugerir que, em um argumento autorreferencial, deve-se procurar mais adiante no caminho de busca. E, honestamente, essa pode ser uma mudança plausível no processo de avaliação do R, mas não é uma mudança pequena de comportamento e, dada a trivialidade da "correção", não sei se seria uma alta prioridade para os desenvolvedores do R Core (não falo por eles).
A maneira mais simples (e talvez óbvia) de não ter esse problema é renomear o argumento (ou a função), como você demonstrou:
Imagino que você esteja perguntando isso porque ou você não tem controle sobre ambas as extremidades por algum motivo, ou você realmente quer que o argumento tenha o mesmo nome de outra função.
Uma maneira reconhecidamente feia de fazer isso poderia ser
Outra opção é ter "processamento interno especial" usando
missing
ouis.null
:Devo observar a aparência de ambiguidade aqui, onde estamos chamando
f1()
na presença de umaf1
função não nula no ambiente. @user2554330 também menciona isso, uma anomalia em todo o processo de "encontrar algo" que o R segue. Não sei por que essa diferenciação não ocorre na avaliação de argumentos, mas não ocorre (no momento).Na mesma linha, podemos manter o argumento formal autodocumentado de
f1()
in the formals com uma simples verificação interna:Isso é bom para a documentação (que não deve ser ignorada!), mas é o mesmo (computacionalmente) que o
f1=NULL
exemplo acima: act onmissing(f1)
.@r2evans dá uma boa resposta, mas não está completamente correta. Como ele diz, quando o R tenta avaliar,
f1()
ele primeiro procuraf1
no ambiente atual e encontra o argumento formal lá, então a busca termina. Tudo isso está correto.No entanto, uma coisa que ele disse não está totalmente correta: "Em nenhum momento R dirá: 'Bem, este f1 não está funcionando para mim, vamos continuar procurando no caminho de busca'." Isso é verdade neste caso, mas apenas porque
f1
é um argumento formal. Em outras situações, R continua procurando.Por exemplo, suponha que você tivesse definido coisas assim:
Criado em 2025-04-10 com reprex v2.1.1
Neste caso, o R busca no ambiente local por
f1
e encontra a variável que foi definida como 123. Mas essa variável não é uma função, então ele efetivamente diz "bem, issof1
não é o que eu quero", e continua procurando. Ele encontra a função e a utiliza.A diferença em relação ao seu caso original é que é possível determinar que a variável local
f1
não é uma função. No seu caso, ele não sabia o que era, então teve que avaliá-la primeiro — mas já estava no meio da tentativa, então falhou.A propósito, estou escrevendo isso apenas para que você veja alguns dos "processos por trás desse erro". @r2evans oferece boas soluções alternativas se você realmente quiser uma função que se pareça com o seu
f2()
.