O que significa || = (ou-igual) em Ruby?

O que o código a seguir significa em Ruby?

||= 

Tem algum significado ou razão para a syntax?

Essa questão tem sido discutida com tanta freqüência nas listas de discussão Ruby e blogs Ruby que agora existem até mesmo threads na lista de discussão Ruby cuja única finalidade é coletar links para todos os outros tópicos na lista de discussão Ruby que discutem este problema. .

Aqui está uma: A lista definitiva de tópicos e páginas || = (OR Equal)

Se você realmente quer saber o que está acontecendo, dê uma olhada na Seção 11.4.2.3 “Atribuições Abreviadas” da Especificação de Rascunho da Linguagem Ruby .

Como uma primeira aproximação,

 a ||= b 

é equivalente a

 a || a = b 

e não equivalente a

 a = a || b 

No entanto, isso é apenas uma primeira aproximação, especialmente se a for indefinida. A semântica também difere dependendo se é uma atribuição de variável simples, uma atribuição de método ou uma atribuição de indexação:

 a ||= b ac ||= b a[c] ||= b 

são todos tratados de forma diferente.

a ||= b é um “operador de atribuição condicional”. É meio- termo -não-bem (*) abreviação de a || a = b a || a = b .

Significa ” se a é indefinido ou falsey ( false ou nil ), então avalie b e defina a como resultado “.

Por exemplo:

 > a ||= nil => nil > a ||= 0; => 0 > a ||= 2; => 0 > foo = false; => false > foo ||= true; => true > foo ||= false; => true 

A avaliação de curto-circuito de Ruby significa que, se a for definido e avaliado como truthy, o lado direito do operador não é avaliado e nenhuma atribuição ocorre. Essa distinção não é importante se a e b forem variables ​​locais, mas é significativo se um deles for um método getter / setter de uma class.

Confusamente, parece semelhante a outros operadores de atribuição (como += ), mas se comporta de maneira diferente.

a += b traduz em a = a + b

a ||= b traduz aproximadamente para * a || a = b a || a = b

* Exceto que, quando a é indefinido, a || a = b a || a = b seria NameError, enquanto a ||= b define a para b .

Leitura adicional:

Resposta concisa e completa

 a ||= b 

avalia da mesma forma que cada uma das seguintes linhas

 a || a = b a ? a : a = b if a then a else a = b end 

Por outro lado,

 a = a || b 

avalia da mesma forma que cada uma das seguintes linhas

 a = a ? a : b if a then a = a else a = b end 

Edit: Como AJedi32 apontou nos comentários, isso só vale se: 1. a é uma variável definida. 2. Avaliar uma vez e duas vezes não resulta em diferença no estado do programa ou do sistema.

Em suma, a||=b significa: Se a é undefined, nil or false , atribua b a a . Caso contrário, mantenha a intacto.

Basicamente,

x ||= y significa

se x tiver algum valor, deixe-o em paz e não altere o valor, caso contrário, defina x para y .

Isso significa ou-igual a. Ele verifica se o valor à esquerda está definido e, em seguida, usa isso. Se não estiver, use o valor à direita. Você pode usá-lo no Rails para armazenar em cache as variables ​​de instância nos modelos.

Um exemplo rápido baseado em Rails, onde criamos uma function para buscar o usuário atualmente logado:

 class User > ActiveRecord::Base def current_user @current_user ||= User.find_by_id(session[:user_id]) end end 

Ele verifica se a variável da ocorrência @current_user está configurada. Se estiver, ele retornará, salvando assim uma chamada do database. Se não estiver definido, no entanto, fazemos a chamada e, em seguida, definimos a variável @current_user para isso. É uma técnica de cache muito simples, mas é ótima para quando você está buscando a mesma variável de instância em todo o aplicativo várias vezes.

 x ||= y 

é

 x || x = y 

“se x é falso ou indefinido, então x aponta para y”

Para ser preciso, a ||= b significa “se a é indefinida ou false ( false ou nil ), defina a para b e avalie para (ie retorno) b , caso contrário, avalie para”.

Outros freqüentemente tentam ilustrar isso dizendo que a ||= b é equivalente a a || a = b a || a = b ou a = a || b a = a || b . Essas equivalências podem ser úteis para entender o conceito, mas esteja ciente de que elas não são precisas em todas as condições. Permita-me explicar:

  • a ||= ba || a = b a || a = b ?

    O comportamento dessas instruções difere quando a é uma variável local indefinida. Nesse caso, a ||= b irá definir a para b (e avaliar para b ), enquanto a || a = b a || a = b aumentará NameError: undefined local variable or method 'a' for main:Object .

  • a ||= ba = a || b a = a || b ?

    A equivalência dessas declarações é frequentemente assumida, uma vez que uma equivalência semelhante é verdadeira para outros operadores de atribuição abreviados (ou seja, += , -= , *= , /= , %= , **= , &= , |= , ^= , < <= e >>= ). No entanto, para ||= o comportamento dessas instruções pode diferir quando a= é um método em um object e a é verdadeiro. Nesse caso, a ||= b não fará nada (além de avaliar para a ), enquanto a = a || b a = a || b chamará a=(a) no receptor de um. Como outros apontaram, isso pode fazer a diferença quando se chama a=a tem efeitos colaterais, como adicionar chaves a um hash.

  • a ||= ba = b unless a ??

    O comportamento dessas afirmações difere apenas no que elas avaliam quando a é verdadeira. Nesse caso, a = b unless a vontade seja avaliada como nil (embora a vontade ainda não seja definida, como esperado), enquanto a ||= b será avaliada como a .

  • a ||= bdefined?(a) ? (a || a = b) : (a = b) defined?(a) ? (a || a = b) : (a = b) ????

    Ainda não. Essas instruções podem diferir quando existe um método method_missing que retorna um valor geral para a . Neste caso, a ||= b irá avaliar para qualquer method_missing retornos_de_missos, e não tentar definir a , enquanto defined?(a) ? (a || a = b) : (a = b) defined?(a) ? (a || a = b) : (a = b) irá definir a para b e avaliar para b .

Ok, ok, então o que é a ||= b equivalente a? Existe uma maneira de expressar isso em Ruby?

Bem, supondo que eu não esteja negligenciando nada, eu acredito que a ||= b é funcionalmente equivalente a ... ( drumroll )

 begin a = nil if false a || a = b end 

Aguente! Não é apenas o primeiro exemplo com um noop antes disso? Bem, não é bem assim. Lembre-se de como eu disse antes que a ||= b não é equivalente a a || a = b a || a = b quando a é uma variável local indefinida? Bem, a = nil if false garante que a nunca é indefinida, mesmo que essa linha nunca seja executada. Variáveis ​​locais em Ruby são lexicamente definidas.

unless x x = y end

a menos que x tenha um valor (não é nulo ou falso), defina-o como y

é equivalente a

x ||= y

Suponha que a = 2 b = 3

ENTÃO, a ||= b será resultado do valor de um, ou seja, 2 .

Como quando se avalia para algum valor não resultou false ou nil .. É por isso que não ll avaliar o valor de b .

Agora suponha a = nil b = 3 .

Então, a ||= b será resultado do valor de 3 ie b .

Como ele primeiro tenta avaliar o valor de um que resultou em nil .. então ele avaliou o valor de b .

O melhor exemplo usado no aplicativo é:

 #To get currently logged in iser def current_user @current_user ||= User.find_by_id(session[:user_id]) end # Make current_user available in templates as a helper helper_method :current_user 

Onde, User.find_by_id(session[:user_id]) é acionado se e somente se @current_user não for inicializado antes.

Esta é a notação de atribuição padrão

por exemplo: x || = 1
isso irá verificar se x é nulo ou não. Se x for de fato nulo, ele atribuirá a ele aquele novo valor (1 em nosso exemplo)

mais explícito:
se x = nulo
x = 1
fim

 a ||= b 

é equivalente a

 a || a = b 

e não

 a = a || b 

por causa da situação em que você define um hash com um padrão (o hash retornará o padrão para qualquer chave indefinida)

 a = Hash.new(true) #Which is: {} 

se você usar:

 a[10] ||= 10 #same as a[10] || a[10] = 10 

a ainda é:

 {} 

mas quando você escreve assim:

 a[10] = a[10] || 10 

um torna-se:

 {10 => true} 

porque você atribuiu o valor de si mesmo na chave 10 , cujo padrão é true, então agora o hash é definido para a chave 10 , em vez de nunca executar a atribuição em primeiro lugar.

É como uma instanciação preguiçosa. Se a variável já estiver definida, ela pegará esse valor em vez de criar o valor novamente.

 irb(main):001:0> a = 1 => 1 irb(main):002:0> a ||= 2 => 1 

Porque a já estava definida para 1

 irb(main):003:0> a = nil => nil irb(main):004:0> a ||= 2 => 2 

Porque a era nil

Por favor, lembre-se também que ||= não é uma operação atômica e, portanto, não é thread-safe. Como regra geral, não use para methods de class.

 b = 5 a ||= b 

Isso se traduz em:

 a = a || b 

qual será

 a = nil || 5 

então finalmente

 a = 5 

Agora, se você ligar de novo:

 a ||= b a = a || b a = 5 || 5 a = 5 b = 6 

Agora, se você ligar de novo:

 a ||= b a = a || b a = 5 || 6 a = 5 

Se você observar, o valor b não será atribuído a. a ainda terá 5 .

É um padrão de memorização que está sendo usado em Ruby para acelerar os acessadores.

 def users @users ||= User.all end 

Isso basicamente se traduz em:

 @users = @users || User.all 

Então, você fará uma chamada para o database pela primeira vez que chamar esse método.

Futuras chamadas para este método apenas retornarão o valor da variável de instância @users .

como um equívoco comum a || = b não é equivalente a a = a || b mas é, mas se comporta como || a = b

Mas aqui vem um caso complicado

Se a não está definido, || a = 42 gera NameError, enquanto a || = 42 retorna 42. Então, eles não parecem ser expressões equivalentes.

||= é chamado de operador de atribuição condicional.

Funciona basicamente como = mas com a exceção de que, se uma variável já foi atribuída, ela não fará nada.

Primeiro exemplo:

 x ||= 10 

Segundo exemplo:

 x = 20 x ||= 10 

No primeiro exemplo, x é agora igual a 10. No entanto, no segundo exemplo, x já está definido como 20. Portanto, o operador condicional não tem efeito. x ainda é 20 depois de executar x ||= 10 .

a ||= b é o mesmo que dizer a = b if a.nil? ou a = b unless a

Mas todas as 3 opções mostram o mesmo desempenho? Com o Ruby 2.5.1 este

 1000000.times do a ||= 1 a ||= 1 a ||= 1 a ||= 1 a ||= 1 a ||= 1 a ||= 1 a ||= 1 a ||= 1 a ||= 1 end 

leva 0,099 segundos no meu PC, enquanto

 1000000.times do a = 1 unless a a = 1 unless a a = 1 unless a a = 1 unless a a = 1 unless a a = 1 unless a a = 1 unless a a = 1 unless a a = 1 unless a a = 1 unless a end 

leva 0,062 segundos. Isso é quase 40% mais rápido.

e depois também temos:

 1000000.times do a = 1 if a.nil? a = 1 if a.nil? a = 1 if a.nil? a = 1 if a.nil? a = 1 if a.nil? a = 1 if a.nil? a = 1 if a.nil? a = 1 if a.nil? a = 1 if a.nil? a = 1 if a.nil? end 

o que leva 0,166 segundos.

Não que isso cause um impacto significativo no desempenho em geral, mas se você precisar desse último bit de otimização, considere esse resultado. A propósito: a = 1 unless a seja mais fácil de ler para o iniciante, é auto-explicativo.

Nota 1: o motivo para repetir a linha de atribuição várias vezes é reduzir a sobrecarga do loop no tempo medido.

Nota 2: Os resultados são semelhantes se eu fizer a=nil zero antes de cada atribuição.