Why is `a = a` `nil` in Ruby? - ruby

I watched this video. Why is a = a evaluated to nil if a is not defined?
a = a # => nil
b = c = q = c # => nil

Ruby interpreter initializes a local variable with nil when it sees an assignment to it. It initializes the local variable before it executes the assignment expression or even when the assignment is not reachable (as in the example below). This means your code initializes a with nil and then the expression a = nil will evaluate to the right hand value.
a = 1 if false
a.nil? # => true
The first assignment expression is not executed, but a is initialized with nil.
You can find this behaviour documented in the Ruby assignment documentation.

Related

Variable defined despite condition should prevent it

Today I came across an interesting piece of code. It's more like a scientific question about the ruby parser.
We know everything in ruby is an object and every expression evaluates at least to nil.
But how is the following "assignment" parsed:
somevar
NameError: undefined local variable or method 'somevar' for main:Object
somevar = "test" if false
=> nil
somevar
=> nil
You see the variable is undefined until it's used in the assignment. But the assignment is not happening because of the condition. Or is it happening because the condition evaluates to nil? I tried something which would break in this case, but it just works:
a = {}
a[1/0]
ZeroDivisionError: divided by 0
a[1/0] = "test" if false
=> nil
So is this meant to work the way it is? Or does it make sense to test the variable (defined?(somevar)) before accessing, in case a future version of ruby will break this behaviour? As example by saving the assigned pointer to this variable.
My currently used ruby version is 3.0.2.
This is expected behavior in Ruby. Quote from the Ruby docs:
The local variable is created when the parser encounters the assignment, not when the assignment occurs:
a = 0 if false # does not assign to a
p local_variables # prints [:a]
p a # prints nil
If you do = "test" if false it evaluates to nil => no assignment needed. But by calling somevar = ... you told the interpreter to declare the name somevar. The nil aren't the same (if that makes sense).
The [] operator however doesn't declare a variable (only accesses) but since if false isn't true there is no assignment so the whole left side isnt evaluated.
Consider:
a = [1,2,3]
a[1] = "test" if false
a
=> [1,2,3]
a[1] is neither nil nor test.
Not sure what you expect or how future Ruby will break this?

`defined?` and `unless` not working as expected

I was expecting the following snippet:
var = "Not Empty" unless defined? var
var # => nil
to return "Not Empty", but I got nil. Any insight into why this is happening?
This is one of the only moments in Ruby I would call actual WTFs.
You have to use
unless defined? var
var = :value
end
With the postfix syntax, the interpreter will internally nil-ify the value so it can reason about the variable, thus making it defined before the check is done:
# Doesn't print anything
unless defined?(foo) and (p(foo) or true)
foo = :value
end
# Prints nil
bar = :value unless defined?(bar) and (p(bar) or true)
Local variables are defined (as nil) at the point they are parsed. Definition of var2 precedes the condition. That makes var2 defined even when if the assignment is not executed. Then, the condition evaluates that var2 is defined, which retains the value nil for var2.

How is a local variable created even when IF condition evaluates to false in Ruby? [duplicate]

This question already has answers here:
Confusion with the assignment operation inside a falsy `if` block [duplicate]
(3 answers)
Closed 5 years ago.
Try the following in irb: (I'm using Ruby 2.0.0-p247)
blah
#=> NameError: undefined local variable or method `blah' for main:Object
if false
blah = 'blah'
end
#=> nil
blah
#=> nil
I'm surprised that blah is assigned nil even when the if condition evaluates to false.
I thought the code within if is skipped as the condition evaluates to false.
Could someone with Ruby internals knowledge kindly explain how this happened?
Thank you
Local variables in ruby are created during parsing/compilation of code (not execution). They are lexically scoped, so a local variable is not visible before the line where it's assigned to.
defined?(foo) # => nil
if false
defined?(foo) # =>
foo = 'blah'
defined?(foo) # =>
end
defined?(foo) # => "local-variable"
foo # => nil
defined?(foo) lines inside of if return nothing, because they didn't run. The assignment wasn't executed as well. However, the compiler saw the assignment to local variable and created one (with default value of nil).
This behaviour explains the trick from WAT talk:
a = a # => nil
Even though variable a doesn't exist, it is created (and set to nil) right before this line, simply because there is an assignment expression in the code (target of which is yet unknown local variable). So by the time the right hand side of this expression is evaluated, a exists.

Variable getting initialized with nil

p b #undefined local variable or method b for main:Object
a = nil
if a and (b=3)
do_something_with b
end
p b # nil
Why is b getting the value nil after the execution of if block, while expected result would be undefined local variable or method b for main:Object, Does Ruby initialize all the variables to nil in the memory beforehand ?
The same case with the following code
if nil
bb = 10
end
p bb # nil
someone please throw some light on how ruby initializes the variables and what is going on in this case, Thanks
"[A local variable] is initialized if it appears on the left‐hand side (before the equals sign (U+003D)) of an assignment expression, even if the expression does not actually execute. Variables of the latter sort have the value nil."
EDIT: This answer used to point to a fairly good Ruby reference, which has apparently been replaced by a malware site. I've removed the link but retained the quotation of the answer.

what is the value of if/unless modifier?

I could not find anywhere a formal specification of the if (or unless) modifier:
123 if x > 0
what is the value of the above statement if x is not above zero? irb suggests nil, but is it documented anywhere?
(yes, this is perhaps a stupid question, sorry, could not find a spec).
An expression that is not evaluated is the same as not existing. And your condition should be part of some code block such as definition or the main environment, etc. A code block without a content is evaluated to nil.
class A; end
# => nil
def foo; end
foo
# => nil
()
# => nil
begin; end
# => nil
eval("")
# => nil
So the reason your example returns nil has nothing to do with condition itself. It is just due to there being no evaluation.
Yes, it is nil. What else could it return? The statement in the if clause is used for the if itself, and the then statement is not executed. There is nothing to return, so nil.
Relevant spec
it "returns nil if else-body is empty and expression is false" do
if false
123
else
end.should == nil
end

Resources