Why is `a => a` different to `a = a` in Ruby? - ruby

I understand why a = a in Ruby produces nil (see Why is a = a nil in Ruby?).
But using the rightwards assignment operator, a => a produces an error:
irb(main):001:0> a => a
(irb):1:in `<main>': undefined local variable or method `a' for main:Object (NameError)
But when the right hand side is not a complex pattern, I thought => should be exactly the same as =, just flipped.
So why does a => a not behave the same as a = a?

From the pattern matching docs we know that:
<expression> in <pattern> is the same as case <expression>; in <pattern>; true; else false; end.
so:
a in a
is actually a shorthand for:
case a
in a
true
else
false
end
In the above case expression it's probably obvious that you would get a NameError on the very first line.
I assume a => a behaves similarly to a in a in regards to evaluation order.

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?

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

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

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.

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 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