¿Qué significa el siguiente código en Ruby?
||=
¿Tiene algún significado o razón para la sintaxis?
¿Qué significa el siguiente código en Ruby?
||=
¿Tiene algún significado o razón para la sintaxis?
Respuestas:
Esta pregunta se ha discutido con tanta frecuencia en las listas de correo de Ruby y en los blogs de Ruby que ahora hay incluso hilos en la lista de correo de Ruby cuyo único propósito es recopilar enlaces a todos los otros hilos en la lista de correo de Ruby que discuten este tema .
Aquí hay uno: la lista definitiva de hilos y páginas || = (O igual)
Si realmente quiere saber lo que está sucediendo, eche un vistazo a la Sección 11.4.2.3 "Asignaciones abreviadas" de la Especificación del borrador del lenguaje Ruby .
Como primera aproximación,
a ||= b
es equivalente a
a || a = b
y no equivalente a
a = a || b
Sin embargo, esa es solo una primera aproximación, especialmente si a
no está definida. La semántica también difiere dependiendo de si es una asignación de variable simple, una asignación de método o una asignación de indexación:
a ||= b
a.c ||= b
a[c] ||= b
Todos son tratados de manera diferente.
a = false; a ||= true
no , no hacer lo que su respuesta dice que lo hace un "matiz".
a ||= b
es un operador de asignación condicional . Significa que si a
no está definido o es falso , evalúe b
y establezca a
el resultado . De manera equivalente, si a
se define y se evalúa como verdadero, b
no se evalúa y no se realiza ninguna asignación. Por ejemplo:
a ||= nil # => nil
a ||= 0 # => 0
a ||= 2 # => 0
foo = false # => false
foo ||= true # => true
foo ||= false # => true
Confusamente, se parece a otros operadores de asignación (como +=
), pero se comporta de manera diferente.
a += b
se traduce en a = a + b
a ||= b
aproximadamente se traduce en a || a = b
Es una taquigrafía casi para a || a = b
. La diferencia es que, cuando a
no está definido, a || a = b
aumentaría NameError
, mientras que se a ||= b
establece a
en b
. Esta distinción no es importante si son variables locales a
y b
ambas, pero es significativa si se trata de un método getter / setter de una clase.
Otras lecturas:
h = Hash.new(0); h[1] ||= 2
. Consideremos ahora las dos posibles expansiones h[1] = h[1] || 2
vs h[1] || h[1] = 2
. Ambas expresiones evalúan 0
pero la primera aumenta innecesariamente el tamaño del hash. Quizás es por eso que Matz decidió hacer que se ||=
comportara más como la segunda expansión. (
a || a = b
plantea un NameError
si a
no está definido. a ||= b
no lo hace, sino que se inicializa a
y lo establece en b
. Esa es la única distinción entre los dos, que yo sepa. Del mismo modo, la única diferencia entre a = a || b
y de la a ||= b
que tengo conocimiento es que si se a=
trata de un método, se llamará independientemente de lo que a
regrese. Además, la única diferencia entre a = b unless a
y a ||= b
que conozco es que esa declaración se evalúa en nil
lugar de a
si a
es verdadera. Muchas aproximaciones, pero nada equivalente ...
a ||= b
evalúa de la misma manera que cada una de las siguientes líneas
a || a = b
a ? a : a = b
if a then a else a = b end
-
Por otra parte,
a = a || b
evalúa de la misma manera que cada una de las siguientes líneas
a = a ? a : b
if a then a = a else a = b end
-
Editar: como AJedi32 señaló en los comentarios, esto solo es cierto si: 1. a es una variable definida. 2. Evaluar una vez y dos veces no da como resultado una diferencia en el estado del programa o sistema.
a
es falso / cero / indefinido, se evalúa dos veces. (Pero no conozco a Ruby, así que no sé si los valores pueden ser 'evaluados' exactamente ...)
a || a = b
, a ? a : a = b
, if a then a else a = b end
, Y if a then a = a else a = b end
generará un error si a
no está definido, mientras que a ||= b
y a = a || b
lo hará no. También, a || a = b
, a ? a : a = b
, if a then a else a = b end
, a = a ? a : b
, y if a then a = a else a = b end
evaluar a
dos veces cuando a
es Truthy, mientras que a ||= b
y a = a || b
no lo hacen.
a || a = b
no se evaluará a
dos veces cuando a
sea cierto.
the end state will be equivalent after the whole line has been evaluated
Sin embargo, eso no es necesariamente cierto. ¿Qué pasa si a
es un método? Los métodos pueden tener efectos secundarios. Por ejemplo public; def a=n; @a=n; end; def a; @a+=1; end; self.a = 5
, con , self.a ||= b
devolverá 6, pero self.a ? self.a : self.a = b
devolverá 7.
Significa o-es igual a. Comprueba si el valor de la izquierda está definido, luego úsalo. Si no es así, use el valor de la derecha. Puede usarlo en Rails para almacenar en caché variables de instancia en modelos.
Un ejemplo rápido basado en Rails, donde creamos una función para recuperar el usuario actualmente conectado:
class User > ActiveRecord::Base
def current_user
@current_user ||= User.find_by_id(session[:user_id])
end
end
Comprueba si la variable de instancia @current_user está configurada. Si es así, lo devolverá, guardando así una llamada a la base de datos. Sin embargo, si no está configurado, hacemos la llamada y luego configuramos la variable @current_user para eso. Es una técnica de almacenamiento en caché realmente simple, pero es excelente cuando se busca la misma variable de instancia en la aplicación varias veces.
undefined
, sino que también se activa false
y nil
, lo que puede no ser relevante current_user
, pero especialmente false
puede ser inesperado en otros casos
x ||= y
es
x || x = y
"si x es falso o indefinido, entonces x apunta a y"
Para ser precisos, a ||= b
significa "si a
no está definido o es falso ( false
o nil
), se establece a
en b
y se evalúa como (es decir, retorno) b
, de lo contrario se evalúa como a
".
Otros a menudo intentan ilustrar esto diciendo que a ||= b
es equivalente a a || a = b
o a = a || b
. Estas equivalencias pueden ser útiles para la comprensión del concepto, pero tenga en cuenta que son no precisa en todas las condiciones. Permítame explicarle:
a ||= b
⇔a || a = b
?
El comportamiento de estas declaraciones difiere cuando a
es una variable local indefinida. En ese caso, a ||= b
se establecerá a
en b
(y se evaluará en b
), mientras a || a = b
que aumentará NameError: undefined local variable or method 'a' for main:Object
.
a ||= b
⇔a = a || b
?
La equivalencia de estos estados se asume a menudo, ya que una equivalencia similares es cierto para otros asignación abreviado operadores (es decir +=
, -=
, *=
, /=
, %=
, **=
, &=
, |=
, ^=
, <<=
, y >>=
). Sin embargo, ||=
el comportamiento de estas declaraciones puede diferir cuando a=
es un método sobre un objeto y a
es verdadero. En ese caso, a ||= b
no hará nada (excepto evaluar a a
), mientras a = a || b
que llamará a=(a)
al a
receptor. Como otros han señalado, esto puede marcar la diferencia cuando las llamadas a=a
tienen efectos secundarios, como agregar claves a un hash.
a ||= b
⇔a = b unless a
??
El comportamiento de estas declaraciones difiere solo en lo que evalúan cuando a
es verdadero. En ese caso, a = b unless a
evaluará a nil
(aunque a
todavía no se establecerá, como se esperaba), mientras a ||= b
que evaluará a a
.
a ||= b
⇔defined?(a) ? (a || a = b) : (a = b)
????
Aún no. Estas declaraciones pueden diferir cuando method_missing
existe un método que devuelve un valor verdadero para a
. En este caso, a ||= b
se evaluará a cualquier method_missing
devoluciones, y no tratar de conjunto a
, mientras que defined?(a) ? (a || a = b) : (a = b)
establecerá a
a b
y evaluar a b
.
Vale, vale, así que lo que es a ||= b
equivalente a? ¿Hay alguna manera de expresar esto en Ruby?
Bueno, suponiendo que no estoy pasando por alto nada, creo que a ||= b
es funcionalmente equivalente a ... ( redoble de tambores )
begin
a = nil if false
a || a = b
end
¡Espere! ¿No es ese el primer ejemplo con un noop antes? Bueno, no del todo. ¿Recuerdas cómo dije antes que eso a ||= b
no es equivalente a a || a = b
cuándo a
es una variable local indefinida? Bueno, a = nil if false
asegura que a
nunca esté indefinido, aunque esa línea nunca se ejecute. Las variables locales en Ruby tienen un alcance léxico.
(a=b unless a) or a
a
es un método, se llamará dos veces en lugar de una vez (si devuelve un valor verdadero la primera vez). Eso podría causar que los comportamientos difieran si, por ejemplo, a
tarda mucho tiempo en regresar o si tiene efectos secundarios.
b
aa
, no sigue asignando la rhs a la lhs, o en otras palabras, la lhs todavía no establece su valor en la rhs?
a ||= b
respuesta que he encontrado en Internet. Gracias.
Supongamos a = 2
yb = 3
ENTONCES, se a ||= b
dará como resultado a
el valor de ie 2
.
Como cuando se evalúa a un valor que no resultó false
o nil
... Es por eso que ll
no evalúa b
el valor.
Ahora suponga a = nil
y b = 3
.
Entonces se a ||= b
dará como resultado el valor de 3
ie b
.
Como primero trata de evaluar el valor de a que resultó en nil
... así evaluó b
el valor de.
El mejor ejemplo utilizado en la aplicación ror es:
#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
Donde, User.find_by_id(session[:user_id])
se dispara si y solo si @current_user
no se inicializa antes.
a || = b
Significa si algún valor está presente en 'a' y no desea modificarlo, siga usando ese valor, de lo contrario, si 'a' no tiene ningún valor, use el valor de 'b'.
Palabras simples, si el lado izquierdo si no es nulo, apuntan al valor existente, de lo contrario apuntan al valor en el lado derecho.
a ||= b
es equivalente a
a || a = b
y no
a = a || b
debido a la situación en la que define un hash con un valor predeterminado (el hash devolverá el valor predeterminado para cualquier clave indefinida)
a = Hash.new(true) #Which is: {}
si utiliza:
a[10] ||= 10 #same as a[10] || a[10] = 10
a es todavía:
{}
pero cuando lo escribes así:
a[10] = a[10] || 10
a se convierte en:
{10 => true}
porque ha asignado el valor de sí mismo en la clave 10
, que por defecto es verdadero, por lo que ahora el hash está definido para la clave 10
, en lugar de nunca realizar la asignación en primer lugar.
Recuerde también que ||=
no es una operación atómica y, por lo tanto, no es segura para subprocesos. Como regla general, no lo use para métodos de clase.
Esta es la notación de asignación predeterminada
por ejemplo: x || = 1
esto verificará si x es nulo o no. Si x es nulo, entonces le asignará ese nuevo valor (1 en nuestro ejemplo)
más explícito:
si x == nil
x = 1
final
nil
o false
no sólonil
Si X
NO tiene un valor, se le asignará el valor de Y
. De lo contrario, conservará su valor original, 5 en este ejemplo:
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
||=
asigna valor a la derecha solo si izquierda == nulo (o no está definido o es falso).
Esta sintaxis de ruby-lang. La respuesta correcta es verificar la documentación de ruby-lang. Todas las demás explicaciones se ofuscan .
"Ruby-lang docs Asignación abreviada".
https://docs.ruby-lang.org/en/2.4.0/syntax/assignment_rdoc.html#label-Abbreviated+Assignment
b = 5
a ||= b
Esto se traduce en:
a = a || b
Cuál podría ser
a = nil || 5
así que finalmente
a = 5
Ahora, si vuelves a llamar a esto:
a ||= b
a = a || b
a = 5 || 5
a = 5
b = 6
Ahora, si vuelves a llamar a esto:
a ||= b
a = a || b
a = 5 || 6
a = 5
Si observa, el b
valor no se asignará a a
. a
aún lo tendré 5
.
Es un patrón de memorización que se está utilizando en Ruby para acelerar los accesores.
def users
@users ||= User.all
end
Esto básicamente se traduce en:
@users = @users || User.all
Entonces hará una llamada a la base de datos por primera vez que llame a este método.
Las futuras llamadas a este método solo devolverán el valor de la @users
variable de instancia.
||=
se llama operador de asignación condicional.
Básicamente funciona como, =
pero con la excepción de que si una variable ya ha sido asignada, no hará nada.
Primer ejemplo:
x ||= 10
Segundo ejemplo
x = 20
x ||= 10
En el primer ejemplo x
ahora es igual a 10. Sin embargo, en el segundo ejemplo x
ya está definido como 20. Por lo tanto, el operador condicional no tiene ningún efecto. x
sigue siendo 20 después de correr x ||= 10
.
a ||= b
es lo mismo que decir a = b if a.nil?
oa = b unless a
¿Pero las 3 opciones muestran el mismo rendimiento? Con Ruby 2.5.1 esto
1000000.times do
a ||= 1
a ||= 1
a ||= 1
a ||= 1
a ||= 1
a ||= 1
a ||= 1
a ||= 1
a ||= 1
a ||= 1
end
toma 0.099 segundos en mi PC, mientras
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
toma 0.062 segundos. Eso es casi un 40% más rápido.
y luego también tenemos:
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 toma 0.166 segundos.
No es que esto tenga un impacto significativo en el rendimiento en general, pero si necesita ese último bit de optimización, considere este resultado. Por cierto: a = 1 unless a
es más fácil de leer para el novato, se explica por sí mismo.
Nota 1: la razón para repetir la línea de asignación varias veces es reducir la sobrecarga del bucle en el tiempo medido.
Nota 2: Los resultados son similares si hago a=nil
cero antes de cada tarea.