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


340

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

||=

Tem algum significado ou razão para a sintaxe?

Respostas:


175

Essa questão foi discutida com tanta frequência nas listas de discussão e blogs Ruby que agora existem até mesmo tópicos na lista de discussão Ruby cujo único objetivo é coletar links para todos os outros tópicos na lista de discussão Ruby que discutem esse problema. .

Aqui está um: A lista definitiva de || = (OU iguais) threads e páginas

Se você realmente quer saber o que está acontecendo, consulte a Seção 11.4.2.3 "Atribuições abreviadas" da Especificação de Rascunho no Ruby Language .

Como primeira aproximação,

a ||= b

é equivalente a

a || a = b

e não equivalente a

a = a || b

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

a    ||= b
a.c  ||= b
a[c] ||= b

todos são tratados de forma diferente.


2
O segundo link sofreu rotatividade de bits (comentário de meta por stackoverflow.com/users/540162/nightfirecat ).
Andrew Grimm

330
Essa é uma não resposta muito enigmática. A resposta curta parece ser: a || = b significa que, se a não for definido, atribua o valor de b, caso contrário, deixe-o em paz. (Ok, há nuances e casos especiais, mas esse é o caso básico.)
Steve Bennett

20
@SteveBennett: Eu não chamaria o fato de que a = false; a ||= truese não fazer o que sua resposta diz que faz uma "nuance".
Jörg W Mittag 03/02

23
Talvez essa pergunta tenha sido feita tantas vezes porque as pessoas continuam respondendo que essa pergunta já foi feita tantas vezes.
precisa saber é o seguinte

8
Com esta resposta, é fácil ver por que existem vários threads. Se você tentar procurar uma resposta para essa pergunta usando um chapéu para iniciantes, notará que todas as respostas não estão claras. Por exemplo, com este você está apenas dizendo o que não é. Sugiro para melhorar a sua resposta e dar uma resposta fácil para os novatos: a = b, a menos que um
Arnold Roa

594

a ||= bé um operador de atribuição condicional . Significa se aé indefinido ou falsey , então avalie be defina ao resultado . De maneira equivalente, se afor definido e avaliado como verdadeiro, bnão será avaliado e nenhuma atribuição ocorrerá. Por exemplo:

a ||= nil # => nil
a ||= 0 # => 0
a ||= 2 # => 0

foo = false # => false
foo ||= true # => true
foo ||= false # => true

De maneira confusa, ele se parece com outros operadores de atribuição (como +=), mas se comporta de maneira diferente.

  • a += b traduz para a = a + b
  • a ||= b traduz aproximadamente para a || a = b

É quase um atalho para a || a = b. A diferença é que, quando aé indefinido, a || a = baumentaria NameError, enquanto a ||= bdefinido acomo b. Essa distinção não é importante se ae bsão ambas variáveis ​​locais, mas é significativa se for um método getter / setter de uma classe.

Leitura adicional:


52
Obrigado por esta resposta, faz muito mais sentido.
Tom Hert

não procurou o suficiente, mas ainda não entendeu por que você usaria isso em oposição a a = a || b. talvez apenas a minha opinião pessoal, mas um pouco ridículo que existe tal nuance ...
dtc

2
@ dtc, considere h = Hash.new(0); h[1] ||= 2. Agora, considere os dois possíveis expansões h[1] = h[1] || 2vs h[1] || h[1] = 2. Ambas as expressões são avaliadas, 0mas a primeira aumenta desnecessariamente o tamanho do hash. Talvez por isso Matz tenha optado por se ||=comportar mais como a segunda expansão. (Eu baseei este em um exemplo de um dos tópicos ligados a em outra resposta.)
antinome

11
Eu gosto da outra resposta para saber em que profundidade ela vai, mas eu amo essa resposta por sua simplicidade. Para quem está aprendendo Ruby, esse é o tipo de resposta que precisamos. Se soubéssemos o que || = significava, provavelmente a pergunta teria sido redigida de maneira diferente.
OBCENEIKON

11
Fyi, a || a = bgera um NameErrorse aé indefinido. a ||= bnão, mas o inicializa ae define como b. Essa é a única distinção entre os dois, tanto quanto eu sei. Da mesma forma, a única diferença entre a = a || be a ||= bque estou ciente é que, se a=for um método, ele será chamado independentemente do que aretornar. Além disso, a única diferença entre a = b unless ae a ||= bque estou ciente é que essa declaração é avaliada em nilvez de ase aé verdadeira. Lotes de aproximações, mas nada equivalente ...
Ajedi32

32

Resposta concisa e completa

a ||= b

avalia da mesma maneira 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 maneira 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ó é válido se: 1. a é uma variável definida. 2. Avaliar uma vez e duas vezes não resulta em uma diferença no estado do programa ou do sistema.


11
você está certo? Isso implica que, se afor falso / zero / indefinido, será avaliado duas vezes. (Mas eu não sei Ruby, então eu não sei se lvalues pode ser 'avaliada' exatamente ...)
Steve Bennett

Entendo o que você está dizendo. O que eu quis dizer com duas linhas equivalentes é que o estado final será equivalente depois que toda a linha for avaliada, significando o valor de a, be o que é retornado. Se intérpretes ruby ​​usam ou não estados diferentes - como várias avaliações de a - para chegar lá é inteiramente possível. Algum especialista em intérpretes de rubi por aí?
the_minted

3
Isso não está certo. a || a = b, a ? a : a = b, if a then a else a = b end, E if a then a = a else a = b endirá lançar um erro se anão estiver definida, enquanto que a ||= be a = a || bnão vai. Além disso, a || a = b, a ? a : a = b, if a then a else a = b end, a = a ? a : b, e if a then a = a else a = b endavaliar aduas vezes quando aé truthy, enquanto a ||= be a = a || bnão.
Ajedi32

11
* correção: a || a = bnão avalia aduas vezes quando aé verdade.
Ajedi32

11
@the_minted the end state will be equivalent after the whole line has been evaluatedIsso não é necessariamente verdade. E se afor um método? Métodos podem ter efeitos colaterais. Por exemplo public; def a=n; @a=n; end; def a; @a+=1; end; self.a = 5, With self.a ||= bretornará 6, mas self.a ? self.a : self.a = bretornará 7. #
Ajedi32

27

Em resumo, a||=bsignifica: Se aestiver undefined, nil or false, atribua ba a. Caso contrário, mantenha-se aintacto.


16
Basicamente,


x ||= y significa

se xtiver algum valor, deixe em branco e não altere o valor; caso contrário, defina xcomoy


13

Significa ou é igual a. Ele verifica se o valor à esquerda está definido e use-o. Caso contrário, use o valor à direita. Você pode usá-lo no Rails para armazenar em cache variáveis ​​de instância em modelos.

Um exemplo rápido do Rails, em que criamos uma função para buscar o usuário conectado no momento:

class User > ActiveRecord::Base

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

end

Ele verifica se a variável de instância @current_user está configurada. Se for, ele retornará, salvando uma chamada ao banco de dados. Se não estiver definido, no entanto, fazemos a chamada e definimos a variável @current_user para isso. É uma técnica de cache realmente simples, mas é ótima para quando você está buscando a mesma variável de instância no aplicativo várias vezes.


8
Isto está errado. Por favor, leia Ruby-Forum.Com/topic/151660 e os links fornecidos nele.
Jörg W Mittag

11
@Jo (trema) rg, eu não estou vendo o que há de errado nisso. Seu link é uma lista de outros links. Nenhuma explicação real do porquê está errado, apenas soa como um julgamento de valor do seu lado.
eggmatters

essa resposta está errada, porque não só dispara em undefined, mas também falsee nil, o que pode não ser relevante para current_user, mas especialmente o falsepode ser unexpectecd em outros casos
dfherr

Apesar de qualquer incompletude que esta resposta possa exibir (não funciona por nada / falso), é a primeira que explica por que você deseja usar || =, então obrigado!
Jonathan Tuzman 11/11


8

Para ser mais preciso, a ||= bmeios "se afor indefinido ou Falsas ( falseou nil), definida apara be avaliar a (ou seja, o retorno) b, de outro modo para avaliar a".

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

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

    O comportamento dessas instruções difere quando aé uma variável local indefinida. Nesse caso, a ||= bserá definido apara b(e avaliar a b), ao passo que a || a = belevar a vontade NameError: undefined local variable or method 'a' for main:Object.

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

    A equivalência destas declarações são frequentemente assumido, desde a equivalência semelhante é verdadeiro para outras de atribuição abreviado operadores (ou seja +=, -=, *=, /=, %=, **=, &=, |=, ^=, <<=, e >>=). No entanto, ||=o comportamento dessas instruções pode diferir quando a=é um método em um objeto e aé verdade. Nesse caso, a ||= bnão fará nada (excepto avaliar a a), enquanto que a = a || birá chamar a=(a)em a's receptor. Como outros já apontaram, isso pode fazer a diferença ao chamar a=aefeitos colaterais, como adicionar chaves a um hash.

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

    O comportamento dessas declarações difere apenas no que elas avaliam quando aé verdade. Nesse caso, a = b unless aavaliará como nil(embora aainda não esteja definido como esperado), enquanto a ||= bavaliará como a.

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

    Ainda não. Essas instruções podem diferir quando method_missingexiste um método que retorna um valor verdadeiro para a. Neste caso, a ||= birá avaliar a tudo o que method_missingretorna, e não tentar conjunto a, enquanto que defined?(a) ? (a || a = b) : (a = b)irá definir apara be avaliar a b.

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

Bem, assumindo que eu não estou ignorando nada, acredito que a ||= bseja funcionalmente equivalente a ... ( drumroll )

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

Aguente! Esse não é apenas o primeiro exemplo com um noop antes? Bem, não exatamente. Lembra como eu disse antes que isso a ||= bnão é apenas equivalente a a || a = bquando aé uma variável local indefinida? Bem, a = nil if falsegarante que isso anunca seja indefinido, mesmo que essa linha nunca seja executada. Variáveis ​​locais no Ruby têm escopo lexicamente.


Portanto, seu terceiro exemplo estendido:(a=b unless a) or a
vol7ron

11
@ vol7ron Isso tem um problema semelhante ao # 2. Se afor um método, ele será chamado duas vezes em vez de uma vez (se retornar um valor verdadeiro na primeira vez). Isso pode causar comportamentos diferentes se, por exemplo, ademorar muito para retornar ou tiver efeitos colaterais.
Ajedi32

Além disso, primeira frase, não deveria dizer atribuir baa , o rhs ainda atribui ao lhs, ou em outras palavras, o lhs ainda não atribui seu valor ao rhs?
vol7ron

Melhor a ||= bresposta que encontrei na Internet. Obrigado.
Eric Duminil

3

unless x x = y end

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

é equivalente a

x ||= y


3

Suponha a = 2eb = 3

ENTÃO, a ||= b resultará no avalor de ie 2.

Como quando um avalia para algum valor que não resultou em falseou nil.. É por isso que llnão avalia bo valor de.

Agora Suponha a = nile b = 3.

Então a ||= bresultará no valor do 3ie b.

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

O melhor exemplo usado no aplicativo ror é:

#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_usernão tiver sido inicializado anteriormente.


3

a || = b

Significa se algum valor está presente em 'a' e você não deseja alterá-lo, continue usando esse valor; caso contrário, se 'a' não tiver nenhum valor, use o valor de 'b'.

Palavras simples, se o lado esquerdo não for nulo, aponte para o valor existente, caso contrário, aponte para o valor no lado direito.


2
a ||= b

é equivalente a

a || a = b

e não

a = a || b

devido à situação em que você define um hash com um padrão (o hash retornará o padrão para quaisquer chaves indefinidas)

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

a se torna:

{10 => true}

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


2

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


2

Lembre-se também de que ||=não é uma operação atômica e, portanto, não é segura para threads. Como regra geral, não a use para métodos de classe.


2

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

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

mais explícito:
se x == nil
x = 1
final


um nilou outro false, não apenasnil
Alex Poca

2

|| = é um operador de atribuição condicional

  x ||= y

é equivalente a

  x = x || y

ou alternativamente

if defined?(x) and x
    x = x
else 
    x = y
end

2

Se XNÃO tiver um valor, será atribuído o valor de Y. Senão, preservará seu valor original, 5 neste exemplo:

irb(main):020:0> x = 5
=> 5
irb(main):021:0> y = 10
=> 10
irb(main):022:0> x ||= y
=> 5

# Now set x to nil. 

irb(main):025:0> x = nil
=> nil
irb(main):026:0> x ||= y
=> 10

1

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

Mas aqui vem um caso complicado. Se anão estiver definido, a || a = 42gera NameError, enquanto a ||= 42retorna 42. Portanto, eles não parecem ser expressões equivalentes.


1

||= atribui valor à direita somente se for esquerdo == nil (ou for indefinido ou falso).


você provavelmente quis dizer 'atribui valor à esquerda' em vez de à direita
Maysam Torabi


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

Porque ajá estava definido como1

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

Porque afoinil


Qual é a data da resposta aqui. Por que não está mostrando ano?
Shiv

0
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 novamente:

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

b = 6

Agora, se você ligar novamente:

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

Se você observar, o bvalor não será atribuído a a. aainda terá 5.

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

def users
  @users ||= User.all
end

Isso basicamente se traduz em:

@users = @users || User.all

Portanto, você fará uma chamada ao banco de dados pela primeira vez que chamar esse método.

Chamadas futuras para esse método retornarão apenas o valor da @usersvariável de instância.


0

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

Basicamente, funciona como =mas com a exceção de que se uma variável já tiver sido atribuída, ela não fará nada.

Primeiro exemplo:

x ||= 10

Segundo exemplo:

x = 20
x ||= 10

No primeiro exemplo, xagora é igual a 10. No entanto, no segundo exemplo xjá está definido como 20. Portanto, o operador condicional não tem efeito. xainda é 20 depois de executarx ||= 10 .


-2

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

Mas todas as três opções mostram o mesmo desempenho? Com o Ruby 2.5.1, isso

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 então 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

que leva 0.166 segundos.

Não que isso tenha 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 é mais fácil ler para o novato, é auto-explicativo.

Nota 1: a razão 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=nilnada antes de cada tarefa.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.