O que o código a seguir significa em Ruby?
||=
Tem algum significado ou razão para a sintaxe?
O que o código a seguir significa em Ruby?
||=
Tem algum significado ou razão para a sintaxe?
Respostas:
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 a
for 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.
a = false; a ||= true
se não fazer o que sua resposta diz que faz uma "nuance".
a ||= b
é um operador de atribuição condicional . Significa se a
é indefinido ou falsey , então avalie b
e defina a
o resultado . De maneira equivalente, se a
for definido e avaliado como verdadeiro, b
nã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 = b
aumentaria NameError
, enquanto a ||= b
definido a
como b
. Essa distinção não é importante se a
e b
são ambas variáveis locais, mas é significativa se for um método getter / setter de uma classe.
Leitura adicional:
h = Hash.new(0); h[1] ||= 2
. Agora, considere os dois possíveis expansões h[1] = h[1] || 2
vs h[1] || h[1] = 2
. Ambas as expressões são avaliadas, 0
mas 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.)
a || a = b
gera um NameError
se a
é indefinido. a ||= b
não, mas o inicializa a
e 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 || b
e a ||= b
que estou ciente é que, se a=
for um método, ele será chamado independentemente do que a
retornar. Além disso, a única diferença entre a = b unless a
e a ||= b
que estou ciente é que essa declaração é avaliada em nil
vez de a
se a
é verdadeira. Lotes de aproximações, mas nada equivalente ...
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.
a
for 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 ...)
a || a = b
, a ? a : a = b
, if a then a else a = b end
, E if a then a = a else a = b end
irá lançar um erro se a
não estiver definida, enquanto que a ||= b
e a = a || b
nã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 end
avaliar a
duas vezes quando a
é truthy, enquanto a ||= b
e a = a || b
não.
a || a = b
não avalia a
duas vezes quando a
é verdade.
the end state will be equivalent after the whole line has been evaluated
Isso não é necessariamente verdade. E se a
for 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 ||= b
retornará 6, mas self.a ? self.a : self.a = b
retornará 7. #
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.
undefined
, mas também false
e nil
, o que pode não ser relevante para current_user
, mas especialmente o false
pode ser unexpectecd em outros casos
x ||= y
é
x || x = y
"se x é falso ou indefinido, então x aponta para y"
Para ser mais preciso, a ||= b
meios "se a
for indefinido ou Falsas ( false
ou nil
), definida a
para b
e 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 = b
ou 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 ||= b
⇔a || a = b
?
O comportamento dessas instruções difere quando a
é uma variável local indefinida. Nesse caso, a ||= b
será definido a
para b
(e avaliar a b
), ao passo que a || a = b
elevar a vontade NameError: undefined local variable or method 'a' for main:Object
.
a ||= b
⇔a = 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 ||= b
não fará nada (excepto avaliar a a
), enquanto que a = a || b
irá chamar a=(a)
em a
's receptor. Como outros já apontaram, isso pode fazer a diferença ao chamar a=a
efeitos colaterais, como adicionar chaves a um hash.
a ||= b
⇔a = b unless a
??
O comportamento dessas declarações difere apenas no que elas avaliam quando a
é verdade. Nesse caso, a = b unless a
avaliará como nil
(embora a
ainda não esteja definido como esperado), enquanto a ||= b
avaliará como a
.
a ||= b
⇔defined?(a) ? (a || a = b) : (a = b)
????
Ainda não. Essas instruções podem diferir quando method_missing
existe um método que retorna um valor verdadeiro para a
. Neste caso, a ||= b
irá avaliar a tudo o que method_missing
retorna, e não tentar conjunto a
, enquanto que defined?(a) ? (a || a = b) : (a = b)
irá definir a
para b
e 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 ||= b
seja 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 ||= b
não é apenas equivalente a a || a = b
quando a
é uma variável local indefinida? Bem, a = nil if false
garante que isso a
nunca seja indefinido, mesmo que essa linha nunca seja executada. Variáveis locais no Ruby têm escopo lexicamente.
(a=b unless a) or a
a
for 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, a
demorar muito para retornar ou tiver efeitos colaterais.
b
aa
, o rhs ainda atribui ao lhs, ou em outras palavras, o lhs ainda não atribui seu valor ao rhs?
a ||= b
resposta que encontrei na Internet. Obrigado.
Suponha a = 2
eb = 3
ENTÃO, a ||= b
resultará no a
valor de ie 2
.
Como quando um avalia para algum valor que não resultou em false
ou nil
.. É por isso que ll
não avalia b
o valor de.
Agora Suponha a = nil
e b = 3
.
Então a ||= b
resultará no valor do 3
ie b
.
Como ele primeiro tenta avaliar o valor de a que resultou em nil
... então ele avaliou b
o 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_user
não tiver sido inicializado anteriormente.
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.
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.
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.
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
nil
ou outro false
, não apenasnil
||=
atribui valor à direita somente se for esquerdo == nil (ou for indefinido ou falso).
Essa sintaxe ruby-lang. A resposta correta é verificar a documentação do ruby-lang. Todas as outras explicações ofuscam .
"ruby-lang docs Abreviação de Tarefa".
https://docs.ruby-lang.org/en/2.4.0/syntax/assignment_rdoc.html#label-Abbreviated+Assignment
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 b
valor não será atribuído a a
. a
ainda 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 @users
variável de instância.
||=
é 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, 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 executarx ||= 10
.
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=nil
nada antes de cada tarefa.