Variable in else condition assumed nil value [duplicate] - ruby

This question already has answers here:
Confusion with the assignment operation inside a falsy `if` block [duplicate]
(3 answers)
Why is `a = a` `nil` in Ruby?
(1 answer)
Closed 8 years ago.
This is something strange I've figured out in ruby 1.9.3.
Here is the code:
>> r = true
>> if r
>> a = "hello"
>> else
>> b = "hello"
>> end
Now the value of a is "hello":
>> a
=> "hello"
And strangely the value of b is nil
>> b
=> nil
Since b is nowhere in the scene, it should be undeclared.
Why?

Variable declarations take effect even if the branch where the assignment takes place is never reached.

The reason for this is explained in the Ruby documentation, and it is valid for any version of Ruby, not just 1.9:
The local variable is created when the parser encounters the
assignment, not when the assignment occurs.
This means that if the parser sees an assignment in the code it creates the local variable even if the assignment will never really occur. In this case the value referenced by the variable is nil:
if true
a = 0
else
b = 0
end
p local_variables
# => [:a, :b]
p a
# => 0
p b
# => nil

The local variable is created when the parser encounters the
assignment, not when the assignment occurs:
=> foo
# NameError: undefined local variable or method `foo' for main:Object
=> if false
=> foo = "bar" # does not assign to foo
=> end
=> nil
=> foo
=> nil

Related

Ruby conditions and defined? operator weird behavior

I want to set a variable if it's not already defined, so I write
if defined?(var).nil?
var = true
end
puts "[#{var}]"
This behaves as expected, it will print [true]. But if I want to simplify the snippet and write:
var = true if defined?(var).nil?
puts "[#{var}]"
It will print [].
What is the difference between these two snippets ?
A local variable is defined from the point that the first assignment to it is parsed. Therefore, in your second snippet, the variable is defined at the point where you are calling defined? (since Ruby is parsed like English, i.e. left-to-right, top-to-bottom), therefore the condition is always false, therefore the assignment never gets executed, therefore, the variable never gets initialized. Un-initialized local variables evaluate to nil and nil.to_s is the empty string.
Consider the following.
a #=> NameError (undefined local variable or method `a' for main:Object)
defined?(a)
#=> nil
a #=> NameError (undefined local variable or method `a' for main:Object)
As no local variable a exists defined? returns nil but does not create the variable.
b = 1
c = defined?(b)
#=> "local-variable"
puts "cat" if c
cat
d = nil
e = defined?(nil)
#=> "nil"
e.nil?
#=> false
c ("local-variable") is truthy (logically true) because it is neither nil or false (the latter being falsy).
a #=> NameError (undefined local variable or method `a' for main:Object)
a = true if false
#=> nil
a #=> nil
This last result is an odd characteristic of Ruby. As soon as the parser sees the beginning of the assignment (a =) it sets a equal to nil. I understand this is done for efficiency reasons.
Case 1
d = defined?(var1)
#=> nil
e = d.nil?
#=> true
var1 = true (since e #=> true)
var1
#=> true
Case 2
var2 =
# var2 is set equal to nil
d = defined?(var2)
#=> "nil"
e = d.nil?
#=> false
var2 = true if false
var2
#=> nil
puts "[#{nil}]"
[]

Two similar sentences have different behaviour. Is it ok?

Two similar sentences have different behaviour. Is it ok?
Compare:
a = 123 unless defined? a
a # => nil
but...
unless defined? b
b = 123
end
b # => 123
Yes, this is the correct behaviour. Local variables are created and initialized with nil before assignment. So this code
a = 123 unless defined? a
a # => nil
is a rough equivalent of
a = nil
a = 123 unless defined? a # `a` is not undefined anymore.
a # => nil
Another example (even though c is not defined before this line, this code does not throw a NameError).
c = 2 unless c # => 2

ruby condition sequence

I was under the impression that conditions joined with && were executed in sequence, such that the following would return true:
a = "adasd"
> b = a && b.present?
=> false
Thoughts?
Thanks!
--Peter
note:
b = a
=> "adasd"
b.present?
=> true
When you say this:
b = a && b.present?
You're declaring b as a local variable but it will be nil until the right side of the assignment is evaluated. In particular, b will be nil when you call present? on it and the conjunction will be false making b false.
When you do this:
a = 'pancakes'
b = a
b.present?
b will have the value 'pancakes' when you call present? on it so you get a true return from b.present?.
As per the rails doc
present is checking for a variable to be non blank
As per assignment timing, ruby declares the variable in the scope as soon as it sees it, so b will be in the scope but with no value, so present will return false.
You should maybe compared this with defined?
a = "abc"
=> "abc"
defined? a
=> "local-variable"
defined? b
=> nil
b = defined? b
=> "local-variable"

Set Ruby variable if it is not already defined

In Ruby, how do you set a variable to a certain value if it is not already defined, and leave the current value if it is already defined?
While x ||= value is a way to say "if x contains a falsey value, including nil (which is implicit in this construct if x is not defined because it appears on the left hand side of the assignment), assign value to x", it does just that.
It is roughly equivalent to the following. (However, x ||= value will not throw a NameError like this code may and it will always assign a value to x as this code does not -- the point is to see x ||= value works the same for any falsey value in x, including the "default" nil value):
if !x
x = value
end
To see if the variable has truly not been assigned a value, use the defined? method:
>> defined? z
=> nil
>> z = nil
=> nil
>> defined? z
=> "local-variable"
>> defined? #z
=> nil
>> #z = nil
=> nil
>> defined? #z
=> "instance-variable"
However, in almost every case, using defined? is code smell. Be careful with power. Do the sensible thing: give variables values before trying to use them :)
Happy coding.
#variable ||= "set value if not set"
So false variables will get overridden
> #test = true
=> true
> #test ||= "test"
=> true
> #test
=> nil
> #test ||= "test"
=> "test"
> #test = false
=> false
> #test ||= "test"
=> "test"
As you didn't specify what kind of variable:
v = v
v ||= 1
Don't recommend doing this with local variables though.
Edit: In fact v=v is not needed
If the variable is not defined (declared?) it doesn't exist, and if it is declared then you know how you initialized it, right?
Usually, if I just need a variable whose use I don't yet know---that I know will never use as a Boolean---I initialize it by setting its value to nil. Then you can test if it has been changed later quite easily
x = nil
some code
if x do
[code that will only run if x has changed]
end
that's all.

How can we delete an object (having an integer identifier)?

I would like to delete an object, I can not. Here is an example:
irb(main):001:0> str = "hello"
"hello"
irb(main):003:0> str.object_id
2164703880
irb(main):004:0> str = nil
nil
irb(main):005:0> str.object_id
4
As you can see, I can just set the variable of the object to nil (and then of course its object id will be 4). And after that, the garbage collector will delete automatically the unused object with the id: 2164703880.
But no, I don't want that. I want to remove this object.
Thanks for any ideas, suggestions.
You cannot un-define a local variable in Ruby. You can use remove_class_variable, remove_instance_variable and remove_const, but you can't do this for local variables.
In your code you are actually removing the string object, or at least the garbage collector is removing it. The only thing you keep around is a pointer, named str, that points to nil. But the actual string object will no longer exist.
One way to ensure your variables are un-defined is to wrap them in a Proc. Of course this has the downside of having to create a Proc, and it's much easier to let Ruby perform garbage collection. If you do want to use a proc, it will define it's own binding and you can force local variables, like this:
Proc.new{ |;str| str = "hello"; puts str.object_id }.call
2227691880
=> nil
defined?(str)
=> nil
Keep in mind that Ruby is an Object Oriented programing language, so it's easier to deal with variables inside of objects rather than to worry about globally scoped variables. If your variables are defined inside of functions and objects, they remain local to those functions and objects and will cease to exist once the objects are removed.
I don't think you can, and that's a good thing, because of
>> str = "hello"
str = "hello"
=> "hello"
>> str2 = str
str2 = str
=> "hello"
>> str.object_id
str.object_id
=> 2157491040
>> str2.object_id
str2.object_id
=> 2157491040
>> str = nil
str = nil
=> nil
>> str.object_id
str.object_id
=> 4
>> str2.object_id
str2.object_id
=> 2157491040
>>
I wouldn't want my str2 to disappear because somewhere else in the program str was "removed".

Resources