Variable getting initialized with nil - ruby

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.

Related

Why does a Ruby return with assignment and trailing conditional behave differently with a value assignment?

Consider method f,
def f(a)
return a = 2 if !a.nil?
return 'oh'
end
f(42) # 2
f(nil) # 'oh'
And consider method g,
def g(b)
return a = b if !a.nil?
return 'oh'
end
g(42) # 'oh'
g(nil) # 'oh'
And consider method h,
def h(b)
a = b
return a if !a.nil?
return 'oh'
end
h(42) # 42
h(nil) # 'oh'
I expected g(42) to return 42 ? Why does g(42) not return 42 ?
What is the order of evaluation here that is the difference between f and g, and between g and h?
return a = b if !a.nil?
return 'oh'
is mostly equivalent to
if !a.nil?
return a = b
end
return 'oh'
As such, Ruby first tests whether a is not nil (which is false because a is in fact nil there as it had not been assigned a value yet). Because of that, the body of the if is not executed and the execution follows along to the return 'oh'.
The more important question here is however: why did this work at all and did not result in an error such as
NameError: undefined local variable or method `a'
when trying to access the a variable in the if, even though it was not initialized before.
This works because Ruby initializes variables with nil if they appear on the left-hand side of an assignment in the code, even though the variable may not actually be assigned. This behavior is further explained in e.g. https://stackoverflow.com/a/12928261/421705.
Following this logic, your code thus only works with your original inline-if but would fail with the block-form if as with this longer form, a would only be initialized within the if block. Here, you would thus get the NoMethodError.
It is a matter of lexical parsing as #HolgerJust pointed out.
There are some other similarly interesting side effects of using the modifier-[if/unless]
def a; 1; end;
(a if a = true) == a
#=> false
Here's how the parser sees it in a nutshell:
Define a method a()
The parser then encounters a as part of the then body so it tags this a as a method call (a()) because a is not a local variable at this point and the ruby syntax allows for omission of parentheses in method calls.
The parser then encounters the test expression and here it marks a as a local variable, due to the assignment (=)
The test expression is executed and in process it assigns a the value of true and the test passes
The then body is now executed which calls the a() method, because this is how the reference a was identified in #2, which causes this expression (a if a = true) to return 1.
However as pointed out in #4 the assignment to a has also occurred so this comparison becomes (1) == true
Note: If you remove the method definition this will raise a NameError because of #2 however the local variable assignment will still occur.
begin
c if c = 1
rescue NameError
puts 'Oh'
c
end == c and c == 1
# 'Oh'
#=> true
def g(b)
return a = b if !a.nil?
return 'oh'
end
g(42) # 'oh'
g(nil) # 'oh'
I expected g(42) to return 42 ? Why does g(42) not return 42 ?
There are several potential reasons but the more important question to answer first is this; Why were you expecting 42?
Is it because you thought a was already being explicitly defined in the prior method definition and so it should already have some value? If so, it should be noted that methods starting with a lower case letter are local variables that cannot be accessed outside the method in which they are defined. I'm assuming that this is not your issue and that you already have a proper understanding of variable scope. I thought it might be worth mentioning though just in case--especially for other users who might have a similar problem in the future.
Is it because you thought a was implicitly being assigned a value other than nil? If so, its not. You need to define a before you can test against it. As mentioned by others, you should have probably expected an error if anything since you can't even test against a to see whether or not its nil unless it was already defined.
I can't see any logical reason why someone would expect to see 42 outside those 2 scenarios but maybe I'm missing something.
What is the order of evaluation here that is the difference between f
and g, and between g and h?Why is a always evaluating to anything
other than nil (!nil?) ?
Method g has already been discussed by myself and others. The only reason it works at all instead of throwing an error though is because a is being implicitly assigned a value of nil by lexical parsing/order of operations (see answers already posted by others).
Method f is using a parameter named a so a is being explicitly defined before you test against it.
Method h is also explicitly defining a (a = b and not a = b if) before testing against it.

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?

How to replace 'nil' with a value using sub

I am trying to substitute a nil value with something using sub but it isn't happening. Where am I wrong.
a=""
a.sub!(//,"replaced")
puts a #=> "replaced"
b=nil
b.to_s.sub!(//,"replaced") #tried => (b.to_s).sub!(//,"replaced") but didnt work
puts b #=> nil
What is it that I am missing?
To help you understand what is happening, let's follow your code statement by statement:
a="" # create a new string object (the empty string), assign it to a
a.sub!(//,"replaced") # sub! changes this very string object
puts a #=> "replaced" # the changed string is printed
b=nil # we assign nil to b
b.to_s.sub!(//,"replaced") # this is actually two steps
# nil.to_s creates a new string object "" (the empty string)
# .sub! modifies that new string object in place
# the edited string is not assigned to anything, it will be garbage collected later
puts b #=> nil # b is still assigned to nil
We observe that b itself is never changed by sub!. Solely the object returned by b.to_s is changed (but then discarded).
Are you trying to initialize b? The idiomatic Ruby way to initialize is :
b ||= "replaced"
Whatever you do to b.to_s, it is a different object from b, so b is not modified, and stays to be nil, as assigned initially.
And there is no way to change nil into a string using gsub!. That method is defined on String, not on NilClass. However, you can reassign b to a string just by doing b = whatever_string.
You're not assigning b to the new value "replaced".
b = b.to_s.sub!(//,"replaced")
will help you out otherwise it'll stay nil this is due to to_s providing a temporary representation of your b object and thusly sub! will not affect b at all.
Evidence:
s = "monkey"
s.sub!('m', 'd')
>> "donkey"
Use the following instead:
irb(main):006:0> b = b.to_s.sub(//, "123")
=> "123"
What version of Ruby do you use?
By the way, can you provide more details about what you're doing, because that seems a little weird to me. Maybe we'll give you more appropriate advice.
nil and the empty string isn't the same thing, nil is not a string, and thus it doesn't have the sub! method. However nil.to_s gives the empty string, and your code works
fine here.
irb(main):007:0> b=nil
=> nil
irb(main):008:0> b.to_s.sub!(//,"replaced")
=> "replaced"
Your code doesn't work, because you don't assign the result back to b:
b = nil
b = b.to_s.sub(//,"replaced")
puts b
you need to do this, because, to_s creates a copy of b, which isn't referenced anywhere, and that is the string that sub! changes.
Another solution is to check if b is nil, and set it to "":
b = "" if b.nil?

Why does Ruby seem to hoist variable declarations from inside a case statement even if that code path is not executed? [duplicate]

This question already has answers here:
Why can I refer to a variable outside of an if/unless/case statement that never ran?
(3 answers)
Closed 5 years ago.
We define a function foo:
def foo(s)
case s
when'foo'
x = 3
puts x.inspect
when 'bar'
y = 4
puts y.inspect
end
puts x.inspect
puts y.inspect
end
We then call it as follows:
1.9.3p194 :017 > foo('foo')
in foo scope
3
in outer scope
3
nil
=> nil
1.9.3p194 :018 > foo('bar')
in bar scope
3
in outer scope
nil
3
=> nil
Why does the function not throw an error about an unregistered local variable in either case? In the first case, the variable y seems like it should not exist, so you can't call inspect on it in the outer scope; the same for x in the second case.
Here's another similar example:
def test1
x = 5 if false
puts x.inspect
end
def test2
puts x.inspect
end
And then:
1.9.3p194 :028 > test1
nil
=> nil
1.9.3p194 :029 > test2
NameError: undefined local variable or method `x' for main:Object
What's going on here? It seems like Ruby is hoisting the variable declaration into the outer scope, but I wasn't aware that this is something Ruby does. (Searching for "ruby hoisting" only turns up results about JavaScript hoisting.)
When the Ruby parser sees the sequence identifier, equal-sign, value,
as in this expression
x = 1
it allocates space for a local variable called x. The creation of the
variable—not the assignment of a value to it, but the internal
creation of a variable—always takes place as a result of this kind of
expression, even if the code isn’t executed! Consider this example:
if false
x = 1
end
p x # Output: nil
p y # Fatal Error: y is unknown
The assignment to x isn’t executed, because it’s wrapped in a failing
conditional test. But the Ruby parser sees the sequence x = 1, from
which it deduces that the program involves a local variable x. The
parser doesn’t care whether x is ever assigned a value. Its job is
just to scour the code for local variables for which space needs to
be allocated. The result is that x inhabits a strange kind of variable limbo.
It has been brought into being and initialized to nil.
In that respect, it differs from a variable that has no existence at
all; as you can see in the example, examining x gives you the value
nil, whereas trying to inspect the non-existent variable y results
in a fatal error. But although x exists, it has not played any role in
the program. It exists only as an artifact of the parsing process.
Well-Grounded Rubyist chapter 6.1.2
The ruby parser goes through every lines and set to nil all variable =. The code being actually executed or not does not matter.
See Why can I refer to a variable outside of an if/unless/case statement that never ran?

Ruby: method inexplicably overwritten and set to nil

If I execute this ruby code:
def foo
100
end
p defined?(foo), foo
if false
foo = 200
end
p defined?(foo), foo
The output I get is:
"method"
100
"local-variable"
nil
Can someone explain to me why foo is set to nil after not executing the if? Is this expected behavior or a ruby bug?
Names on the left hand side of assignments get set to nil, even if the code can't be reached as in the if false case.
>> foo
NameError: undefined local variable or method `foo' for main:Object
...
>> if false
.. foo = 1
.. end #=> nil
>> foo #=> nil
When Ruby tries to resolve barewords, it first looks for local variables (there's a reference to that in the Pickaxe book, which I can't seem to find at the moment). Since you now have one called foo it displays nil. As Mischa noted, the method still can be called as foo().
This is what my pal and Ruby super-expert Josh Cheek had to say:
When Ruby sees the assignment, it initializes the variable in the current scope and sets it to nil. Since the assignment didn't get run, it didn't update the value of foo.
if statements don't change scope like blocks do. This is also the most important difference between
for x in xs
and
xs.each { |x| }
Here's another example:
a = 123 if a # => nil
a # => nil
We shouldn't be able to say if a because we never set a, but Ruby sees the a = 123 and initializes a, then gets to if a at which point a is nil
I'd consider it a quirk of the interpreter, really. Gary Bernhardt makes fun of it in wat (https://www.destroyallsoftware.com/talks/wat) with a = a
-Josh

Resources