Como você usa "<< -" (atribuição de escopo) no R?

140

Acabei de ler sobre o escopo na introdução R e estou muito curioso sobre a <<-tarefa.

O manual mostrou um exemplo (muito interessante) para o <<-qual sinto que entendi. O que ainda me falta é o contexto de quando isso pode ser útil.

Então, o que eu gostaria de ler de você são exemplos (ou links para exemplos) de quando o uso de <<-pode ser interessante / útil. Quais podem ser os perigos de usá-lo (parece fácil perder o controle) e quaisquer dicas que você queira compartilhar.

Tal Galili
fonte

Respostas:

196

<<-é mais útil em conjunto com fechamentos para manter o estado. Aqui está uma seção de um artigo recente:

Um fechamento é uma função escrita por outra função. Os fechamentos são assim chamados porque incluem o ambiente da função pai e podem acessar todas as variáveis ​​e parâmetros nessa função. Isso é útil porque nos permite ter dois níveis de parâmetros. Um nível de parâmetros (o pai) controla como a função funciona. O outro nível (a criança) faz o trabalho. O exemplo a seguir mostra como usar essa ideia para gerar uma família de funções de poder. A função pai ( power) cria funções filho ( squaree cube) que realmente fazem o trabalho duro.

power <- function(exponent) {
  function(x) x ^ exponent
}

square <- power(2)
square(2) # -> [1] 4
square(4) # -> [1] 16

cube <- power(3)
cube(2) # -> [1] 8
cube(4) # -> [1] 64

A capacidade de gerenciar variáveis ​​em dois níveis também possibilita manter o estado através de invocações de funções, permitindo que uma função modifique variáveis ​​no ambiente de seu pai. A chave para gerenciar variáveis ​​em diferentes níveis é o operador de atribuição de seta dupla <<-. Diferente da atribuição usual de seta única ( <-) que sempre funciona no nível atual, o operador de seta dupla pode modificar variáveis ​​nos níveis pai.

Isso torna possível manter um contador que registra quantas vezes uma função foi chamada, como mostra o exemplo a seguir. Cada vez que new_counteré executado, ele cria um ambiente, inicializa o contador inesse ambiente e cria uma nova função.

new_counter <- function() {
  i <- 0
  function() {
    # do something useful, then ...
    i <<- i + 1
    i
  }
}

A nova função é um fechamento e seu ambiente é o ambiente envolvente. Quando os fechamentos counter_onee counter_twosão executados, cada um modifica o contador em seu ambiente fechado e, em seguida, retorna a contagem atual.

counter_one <- new_counter()
counter_two <- new_counter()

counter_one() # -> [1] 1
counter_one() # -> [1] 2
counter_two() # -> [1] 1
Hadley
fonte
4
Hey esta é uma tarefa R insolúvel em rosettacode ( rosettacode.org/wiki/Accumulator_factory#R ) Bem, foi ...
Karsten W.
1
Seria necessário incluir mais de 1 fechamentos em uma função pai? Eu apenas tentei um trecho, parece que apenas o último fechamento foi executado ...
mckf111
Existe alguma alternativa de sinal de igual ao sinal "<< -"?
Genom
38

Isso ajuda a pensar <<-como equivalente a assign(se você definir o inheritsparâmetro nessa função como TRUE). O benefício assigné que ele permite que você especifique mais parâmetros (por exemplo, o ambiente), então eu prefiro usar assignmais <<-na maioria dos casos.

Usar <<-e assign(x, value, inherits=TRUE)significa que "os ambientes fechados do ambiente fornecido são pesquisados ​​até que a variável 'x' seja encontrada". Em outras palavras, ele continuará percorrendo os ambientes em ordem até encontrar uma variável com esse nome e a atribuirá a isso. Isso pode estar no escopo de uma função ou no ambiente global.

Para entender o que essas funções fazem, você também precisa entender ambientes R (por exemplo, usando search).

Uso regularmente essas funções quando estou executando uma simulação grande e quero salvar resultados intermediários. Isso permite que você crie o objeto fora do escopo da função ou applyloop especificado . Isso é muito útil, especialmente se você tiver alguma preocupação com um loop grande que termina inesperadamente (por exemplo, uma desconexão do banco de dados); nesse caso, você poderá perder tudo no processo. Isso seria equivalente a gravar seus resultados em um banco de dados ou arquivo durante um longo processo, exceto que, em vez disso, os resultados são armazenados no ambiente R.

Meu aviso principal com isso: tenha cuidado, porque agora você está trabalhando com variáveis ​​globais, especialmente ao usar <<-. Isso significa que você pode acabar com situações em que uma função está usando um valor de objeto do ambiente, quando você esperava que estivesse usando um que foi fornecido como parâmetro. Essa é uma das principais coisas que a programação funcional tenta evitar (consulte os efeitos colaterais ). Eu evito esse problema atribuindo meus valores a nomes de variáveis ​​exclusivos (usando colar com um conjunto ou parâmetros exclusivos) que nunca são usados ​​na função, mas usados ​​apenas para cache e no caso de eu precisar me recuperar mais tarde (ou fazer alguma meta -Análise dos resultados intermediários).

Shane
fonte
3
Obrigado Tal. Eu tenho um blog, embora eu realmente não o use. Eu nunca pode terminar um post, porque eu não quero publicar qualquer coisa a menos que seja perfeito, e eu simplesmente não tenho tempo para isso ...
Shane
2
Um homem sábio me disse uma vez que não é importante ser perfeito - apenas destacando-se - o que você é, e assim serão suas postagens. Além disso - às vezes os leitores ajudam a melhorar o texto com os comentários (é o que acontece com o meu blog). Espero que um dia você vai reconsiderar :)
Tal Galili
9

Um lugar onde eu usei <<-foi em GUIs simples usando tcl / tk. Alguns dos exemplos iniciais têm isso - pois você precisa fazer uma distinção entre variáveis ​​locais e globais para obter a integridade do estado. Veja por exemplo

 library(tcltk)
 demo(tkdensity)

qual usa <<-. Caso contrário, concordo com Marek :) - uma pesquisa no Google pode ajudar.

Dirk Eddelbuettel
fonte
Interessante, de alguma forma não consigo encontrar tkdensityno R 3.6.0.
NelsonGon
1
O pacote tcltk é enviado com R: github.com/wch/r-source/blob/trunk/src/library/tcltk/demo/…
Dirk Eddelbuettel 26/06/19
5
f <- function(n, x0) {x <- x0; replicate(n, (function(){x <<- x+rnorm(1)})())}
plot(f(1000,0),typ="l")
lcgong
fonte
11
Este é um bom exemplo de onde não usar <<-. Um loop for seria mais claro nesse caso.
23410
4

Sobre esse assunto, gostaria de salientar que o <<-operador se comportará estranhamente quando aplicado (incorretamente) em um loop for (pode haver outros casos também). Dado o seguinte código:

fortest <- function() {
    mySum <- 0
    for (i in c(1, 2, 3)) {
        mySum <<- mySum + i
    }
    mySum
}

você pode esperar que a função retorne a soma esperada, 6, mas retorne 0, com uma variável global mySumsendo criada e atribuída o valor 3. Não posso explicar completamente o que está acontecendo aqui, mas certamente o corpo de um para loop não é um novo 'nível' de escopo. Em vez disso, parece que R parece fora da fortestfunção, não consegue encontrar uma mySumvariável à qual atribuir, portanto cria uma e atribui o valor 1, pela primeira vez no loop. Nas iterações subseqüentes, o RHS na atribuição deve estar se referindo à mySumvariável interna (inalterada) enquanto o LHS se refere à variável global. Portanto, cada iteração substitui o valor da variável global pelo valor dessa iteração i, portanto, ele tem o valor 3 ao sair da função.

Espero que isso ajude alguém - isso me deixou perplexo por algumas horas hoje! (BTW, basta substituir <<-por <-e a função funciona conforme o esperado).

Matthew Wise
fonte
2
no seu exemplo, o local mySumnunca é incrementado, mas apenas o global mySum. Portanto, a cada iteração do loop for, o global mySumobtém o valor 0 + i. Você pode seguir isso com debug(fortest).
ClementWalter
Não tem nada a ver com ser um loop for; você está referenciando dois escopos diferentes. Apenas use <-todos os locais de forma consistente na função se você quiser atualizar apenas a variável local dentro da função.
SMCI
Ou use << - em todos os lugares @smci. Embora seja melhor evitar globais.
Estatísticas de aprendizado por exemplo
3

O <<-operador também pode ser útil para classes de referência ao escrever métodos de referência . Por exemplo:

myRFclass <- setRefClass(Class = "RF",
                         fields = list(A = "numeric",
                                       B = "numeric",
                                       C = function() A + B))
myRFclass$methods(show = function() cat("A =", A, "B =", B, "C =",C))
myRFclass$methods(changeA = function() A <<- A*B) # note the <<-
obj1 <- myRFclass(A = 2, B = 3)
obj1
# A = 2 B = 3 C = 5
obj1$changeA()
obj1
# A = 6 B = 3 C = 9
Carlos Cinelli
fonte