Confusión con la operación de asignación dentro de un bloque falso `if`

Estaba jugando con la operación de asignación dentro de if blocks, y descubrí el siguiente resultado, que me sorprendió:

 C:\>irb --simple-prompt if false x = 10 end #=> nil px nil x.object_id #=> 4 #=> nil py NameError: undefined local variable or method `y' for main:Object from (irb):5 from C:/Ruby193/bin/irb:12:in `' 

En el código anterior, puede ver que la variable local x se ha creado aunque solo estaba asignada en el bloque falsy if . Traté de ver el contenido de x con px que me obligó a creer que la asignación no estaba hecha, pero la variable x existe. x.object_id también demostró que ese es el caso.

Ahora mi pregunta es cómo se creó esa variable local x aunque el punto de entrada del bloque if se cierre para siempre intencionalmente.

Esperaba que la salida de px fuera similar a la salida de py . Pero en su lugar recibí una respuesta sorprendente de px .

¿Podría alguien explicarme cómo funciona este concepto?

EDITAR

No, aquí hay otra prueba. Este no es el caso con solo variables local . Lo mismo sucedió con class variables instance y class también. Vea el siguiente:

 class Foo def show @X = 10 if false p @X,"hi",@X.object_id end end #=> nil Foo.new.show nil "hi" 4 #=> [nil, "hi", 4] class Foo def self.show @@X = 10 if false p @@X,"hi",@@X.object_id end end #=> nil Foo.show nil "hi" 4 #=> [nil, "hi", 4] 

Caso exitoso:

 class Foo def self.show @@X = 10 if true p @@X,"hi",@@X.object_id end end #=> nil Foo.show 10 "hi" 4 #=> [10, "hi", 4] 

En Ruby, el analizador define las variables locales cuando encuentra una asignación por primera vez, y luego están en el scope a partir de ese momento.

Aquí hay una pequeña demostración:

 foo # NameError: undefined local variable or method `foo' for main:Object if false foo = 42 end foo # => nil 

Como puede ver, la variable local existe en la línea 7 aunque la asignación en la línea 4 nunca se ejecutó. Sin embargo, fue analizado y por eso existe la variable local foo . Pero debido a que la tarea nunca se ejecutó, la variable no está inicializada y, por lo tanto, se evalúa como nil y no como 42 .

En Ruby, la mayoría de las variables no inicializadas o incluso inexistentes se evalúan como nil . Esto es cierto para variables locales, variables de instancia y variables globales:

 defined? foo #=> nil local_variables #=> [] if false foo = 42 end defined? foo #=> 'local-variable' local_variables #=> [:foo] foo #=> nil foo.nil? #=> true defined? @bar #=> nil instance_variables #=> [] @bar #=> nil @bar.nil? #=> true # warning: instance variable @bar not initialized defined? $baz #=> nil $baz #=> nil # warning: global variable `$baz' not initialized $baz.nil? #=> true # warning: global variable `$baz' not initialized 

Sin embargo, no es verdadero para las variables y constantes de la jerarquía de clases:

 defined? @@wah #=> nil @@wah # NameError: uninitialized class variable @@wah in Object defined? QUUX #=> nil QUUX # NameError: uninitialized constant Object::QUUX 

Esta es una pista falsa:

 defined? fnord #=> nil local_variables #=> [] fnord # NameError: undefined local variable or method `fnord' for main:Object 

La razón por la que se obtiene un error aquí no es que las variables locales unitarias no se evalúen como nil , sino que fnord es ambiguo: podría ser un mensaje enviado sin argumentos al receptor predeterminado (es decir, equivalente a self.fnord() ) o un acceso a la variable local fnord .

Para desambiguar eso, necesita agregar un receptor o una lista de argumentos (incluso si está vacía) para decirle a Ruby que es un mensaje enviado:

 self.fnord # NoMethodError: undefined method `fnord' for main:Object fnord() # NoMethodError: undefined method `fnord' for main:Object 

o asegúrese de que el analizador ( no el evaluador) analiza ( no ejecuta) una tarea antes del uso, para decirle a Ruby que es una variable local:

 if false fnord = 42 end fnord #=> nil 

Y, por supuesto, nil es un objeto (es la única instancia de la clase NilClass ) y, por lo tanto, tiene un método object_id .

Ruby siempre analiza todo tu código. No mira a falso como un signo para no analizar lo que hay adentro, lo evalúa y ve que el código interno no debe ejecutarse

Ruby tiene variable local “izando”. Si tiene una asignación a una variable local en cualquier lugar dentro de un método, esa variable existe en todas partes dentro del método, incluso antes de la asignación, e incluso si la asignación nunca se ejecuta realmente. Antes de que se asigne la variable, tiene un valor de nil .

Editar:

Lo anterior no es del todo correcto. Ruby tiene una forma de elevación variable en el sentido de que definirá una variable local cuando una asignación de variable local está presente, pero no se ejecuta. Sin embargo, no se encontrará que la variable esté definida en puntos en el método anterior donde se produce la asignación.