What does shorthand operator like (+=) in ruby really does? - ruby

While I can understand that x += 1 is equivalent to x = x + 1, I'm interested what it does in the background.
I've tried in irb with x += 1 without first assigning value to x and surely I get an error, it says
NoMethodError (undefined method `+' for nil:NilClass)
When I tried checking x value after it errored, it's now a nil value which isn't quite clear when it was assigned, unless it's done by x += 1 statement.
So, I'm trying to understand how the += does thing as I could not find the documentation for it or it being buried behind the lot of ruby classes. Any pointer would be appreciated.

Ruby treats identifiers as a variables if it sees assignment (=) even if the assignment fails.
irb(main):001:0> a
NameError: undefined local variable or method `a' for main:Object
from (irb):1
from /usr/bin/irb:12:in `<main>'
irb(main):002:0> a = 1 if false
=> nil
irb(main):003:0> a
=> nil
irb(main):004:0>
Even though a=1 never executes, a gets defaulted to nil.

Analysis
Defining our Terms
First of all, + and += are methods, not keywords or operators. More accurately, + is defined as Integer#+, while += is syntactic sugar provided by the interpreter as shorthand for a common operation such as incremented assignment.
Secondly, += and related operations are called abbreviated assignments in Ruby. The full list of supported assignment shorthand items currently include:
["+=",
"-=",
"*=",
"/=",
"%=",
"**=",
"&=",
"|=",
"^=",
"<<=",
">>=",
"||=",
"&&="]
The only non-method operators in Ruby are:
["=", "..", "...", "!", "not", "&&", "and", "||", "or", "!=", "!~"]
and shorthand assignment operators like += are not user-definable.
Defining the Problem
The behavior you're seeing has to do with scope, and how the parser handles block-local variables. Specifically, the Ruby assignment documentation says:
[A] local variable is created when the parser encounters the assignment, not when the assignment occurs[.]
That means that top-level code such as:
x
#=> NameError (undefined local variable or method `x' for main:Object)
raises a NameError exception because defined? x #=> nil. However, the parser auto-instantiates local variables when there's an assignment, so that the following works (sort of), at least in the sense that it doesn't raise NameError:
# x is autovivified as nil
x = x + 1
#=> NoMethodError (undefined method `+' for nil:NilClass)
However, nil doesn't have a NilClass#+ method, so you get NoMethodError instead of NameError. This is because:
nil.respond_to? :+
#=> false
Solutions
The basic solution is to explicitly ensure your variable exists within the current scope, and that it is not nil. You can do this in a variety of ways, but the simplest thing is just to assign it directly. For example:
# assign zero to x unless it's truthy
x ||= 0
# perform incrementing assignment
x += 1
Alternatively, you can drop the shorthand and cast the autovivified x as an Integer, which does have a :+ method. For example, assuming x is currently undefined (e.g. you've restarted irb, or you've set x to nil):
x = x.to_i + 1
#=> 1

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?

How does Ruby handle variable assignment in dead code? [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?

Couldn't understand the difference between `puts{}.class` and `puts({}.class)`

As the anonymous block and hash block looks like approximately same. I was doing kind of playing with it. And doing do I reached to some serious observations as below:
{}.class
#=> Hash
Okay,It's cool. empty block is considered as Hash.
print{}.class
#=> NilClass
puts {}.class
#=> NilClass
Now why the above code showing the same as NilClass,but the below code shows the Hash again ?
puts ({}.class)
#Hash
#=> nil
print({}.class)
#Hash=> nil
Could anyone help me here to understand that what's going one above?
I completely disagree with the point of #Lindydancer
How would you explain the below lines:
print {}.class
#NilClass
print [].class
#Array=> nil
print (1..2).class
#Range=> nil
Why not the same with the below print [].class and print (1..2).class?
EDIT
When ambiguity happens with local variable and method call, Ruby throws an error about the fact as below :
name
#NameError: undefined local variable or method `name' for main:Object
# from (irb):1
# from C:/Ruby193/bin/irb:12:in `<main>'
Now not the same happens with {} (as there is also an ambiguity between empty code block or Hash block). As IRB also here not sure if it's a empty block or Hash. Then why the error didn't throw up when IRB encountered print {}.class or {}.class?
The precedence rules of ruby makes print{}.class interpreted as (print{}).class. As print apparently returns a nil the class method returns #NilClass.
EDIT: As been discussed on other answers and in the updates to the question, print{} it of course interpreted as calling print with a block, not a hash. However, this is still about precedence as {} binds stronger than [] and (1..2) (and stronger than do ... end for that matter).
{} in this case is recognized as block passed to print, while [] unambiguously means empty array.
print {}.class # => NilClass
print do;end.class # => NilClass
You are running into some nuances of Ruby, where characters mean different things depending on context. How the source code is interpreted follows rules, one of which is that {} is a closure block if it follows a method call, and otherwise a Hash constructor.
It's common throughout the language to see characters mean different things depending on context or position within the statement.
Examples:
Parens () used for method call or for precedence
print(1..5).class => NilClass
print (1..5).class => Range <returns nil>
Square brackets [] used to call :[] method or for Array
print[].class => NoMethodError: undefined method `[]' for nil:NilClass
print([].class) => Array <returns nil>
Asterisk * used for multiplication or splatting
1 * 5 => 5
[*1..5] => [1, 2, 3, 4, 5]
Ampersand & used for symbol -> proc or logical and
0 & 1 => 0
[1, 2, 3].map(&:to_s) => ["1", "2", "3"]
Or in your case, braces used for block closures or for a hash
... hope it makes sense now ...

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?

Why in Ruby, a || 1 will throw an error when `a` is undefined, but a = a || 1 will not?

When a is undefined, then a || 1 will throw an error, but a = a || 1 will not. Isn't that a little bit inconsistent?
irb(main):001:0> a
NameError: undefined local variable or method 'a' for main:Object
from (irb):1
from c:/ruby/bin/irb:12:in '<main>'
irb(main):002:0> a || 1
NameError: undefined local variable or method 'a' for main:Object
from (irb):2
from c:/ruby/bin/irb:12:in '<main>'
irb(main):003:0> a = a || 1
=> 1
a
Here, you are evaluating a, which isn't defined. Therefore, you get an exception.
a || 1
Here, you still have to evaluate a to determine the value of the boolean expression. Just like above, a isn't defined. Therefore, you get an exception.
a = a || 1
Here, a is defined. It is defined to be an uninitialized local variable. In Ruby, uninitialized variables evaluate to nil, so the right hand side of the assignment expression evaluates to nil || 1 which evaluates to 1, so the return value of the assignment expression is 1 and the side effect is that a is initialized to 1.
EDIT: It seems that there is some confusion on when variables get defined and when they get initialized in Ruby. The get defined at parse time but initialized at runtime. You can see it here:
foo # => NameError: undefined local variable or method `foo' for main:Object
foo is undefined.
if false
foo = 'This will never get executed'
end
At this point, foo is defined, even though the line will never get executed. The fact that the line never gets executed is completely irrelevant, because the interpreter has nothing to do with this anyway: local variables are defined by the parser, and the parser obviously sees this line.
foo # => nil
There is no error, because foo is defined, and it evaluates to nil because it is uninitialized.
When you do a || 1, you're asking it to look for the value of a which is undefined.
When you do a = a || 1 you're asking it to look for the value of assigning a to a which doesn't seem to give an error.
So, although weird, I don't believe it to be inconsistent.
Is this what you mean?
if !(defined? a) then
a = 1
end
It might be simpler to declare the value with 1 as default.

Resources