Why is assignment treated differently in this single line conditional? - ruby

I've read a few things, including this and this, but I think the below example is different than what they're talking about. One person actually raises a similar example in the discussion but it's ignored.
So, run in irb (ignore the warnings about assignment in the conditional):
(puts x) if (x = 0) # NameError: undefined local variable or method `x'...
x # => 0
(puts x) if (x = 0) # "0", => nil
but the second time there's no error.
Does this make any sense, even in a "once you understand what the parser is really doing and that this just some optimization it all becomes clear" kind of way? Because to me, it seems pretty undesirable.
To be clear, the above conditional should be equivalent (right?) to
if newvar=0
puts newvar
end
which does not raise an error.
Update: There have been even more posts on this topic since this question was asked. For example,
http://seejohncode.com/2012/07/31/ruby-gotcha-single-line-conditionals/.

Oddly enough, this works fine in Rubinius:
Welcome to IRB. You are using rubinius 1.2.4dev (1.8.7 7ae451a1 yyyy-mm-dd JI)
>> (puts x) if (x = 0) #=> nil
0
I'm inclined to say it's a weird parsing bug in MRI.

I think the difference in this case is whether the variable exists when it parses the line. In the case of:
if x=0
puts x
end
the variable x is defined before it parses the line that uses x.
In other words, the error message is a parse time error, not a runtime error.

I think in this you are assigning 0 to x.
(puts x) if (x = 0)
i think it should be
(puts x) if (x == 0)

First, check that you mean x=0 in the conditional clause.
Second, puts x if x = 0 is not equivalent to:
if x = 0
puts x
end
In your case, x is not declared yet for puts x so it cannot see it.

Related

Operator Precedence and Order of Evaluation in Ruby: yield vs << (shovel operator)

I'm reading this book Well-Grounded Rubyist and its Control-Flow Techniques chapter on p.184 has a simple example of implementing map with an iterator:
class Array
def my_map
c=0
acc = []
until c == size
acc << yield self[c]
c += 1 end
acc
end
end
I have the following error when run this code ruby MY_FILE.rb:
MY_FILE.rb:6: syntax error, unexpected `self', expecting `end'
acc << yield self[c]
Solution to this problem is to put brackets around yield self[c], so this whole line would look like this:
acc << (yield self[c])
Then, the routine works with this one-line change. In my understanding the problem is either with operator precedence or with order of evaluation. I surfed the web for quite a while but could not pinpoint why exactly the code from the book does not work.
Why the book example does not work? What is operator precedence or/and order of evaluation in acc << yield self[c]?
Usually, method parameters come in parentheseis:
x=foo(y)
a=bar(baz(z))
b=7+baz(w)
In certain situations (i.e. if precedences does not bind otherwise), you can leave out the braces. Therefore,
x = foo y
works, but
a = bar baz z
is interpreted as
a = (bar(baz))(z)
and
b = 7 + baz w
as
b = (7+baz) w
This is risky, if the interpretation of the expression results in something meaningful. In this case, you would not even get an error message, but the program would simply behave in a different way than you expected.
In general, it is a good idea to always use parenthesis when invoking methods.

How do method-calls interact with operator precedence?

I am curious about how method calls relate to operator precedence. In irb, I see this:
var = puts(5)
5
=> nil
var
=> nil
This implies that the call to puts has higher precedence than the assignment operator, since nil (the return value of puts(5)) is assigned to var, rather than the method call itself. Because nil is assigned to var (as we can see on line 4), I would guess that puts(5) was called before the assignment operator.
In this Stackoverflow thread, everybody agrees that method-calls have lower precedence than every operator.
However this website lists the . as an operator for method-calls, and says that it is the highest-precedence operator.
If this second website is indeed accurate, I'm unsure about whether there is an implicit . operator when you call a method on main (and therefore about whether . being a high-precedence operator is sufficient to explain the irb session above).
In general, I'm curious about the order in which Ruby does things when it encounters a line of code, so if you know of any resources that explain that in an accessible way I would be interested in reading them.
EDIT: thanks for answers so far. Maybe I wasn't clear enough about my basic questions, which are theoretical not practical (so are arguably 'overthinking', depending on how much you like to think):
is . technically an operator, or technically not an operator?
is there a . somewhere behind the scenes every time you call a method?
are operators the basic way that Ruby decides in what order it will evaluate a line of code, or are there factors other than operators and their precedence/associativity/arity?
Thanks
You're overthinking this. Your expression is basically this: x = something. So, right-hand side must be evaluated first, then the assignment can be done.
Here is how to print AST
2.6.3 :008 > RubyVM::AbstractSyntaxTree.parse('x = puts(5)')
=> #<RubyVM::AbstractSyntaxTree::Node:SCOPE#1:0-1:11>
2.6.3 :009 > pp _
(SCOPE#1:0-1:11
tbl: [:x]
args: nil
body:
(LASGN#1:0-1:11 :x
(FCALL#1:4-1:11 :puts (ARRAY#1:9-1:10 (LIT#1:9-1:10 5) nil))))
=> #<RubyVM::AbstractSyntaxTree::Node:SCOPE#1:0-1:11>
I'm using ruby 2.6. This way is possible to solve any parsing doubt. For this case is little obvious as other answers said if you have x = expr, then expr need to be evaluated first since we're talking about a strict language, for lazy languages you will only need to evaluate expr when x is evaluated, but this is another topic
I'm guessing you come from a JavaScript or similar background. Where the following is possible:
function puts(...args) { args.forEach(arg => console.log(arg)); }
var x;
(x = puts)(5);
puts(x);
However in JavaScript calling puts without () will return the whole function. Which allows easy function assignment. However in Ruby calling puts without () will still call the method. Making parentheses optional. See the Calling Methods documentation.
In Ruby (x = puts)(5) would result in a syntax error. You can achieve the same by doing the following:
(x = method(:puts)).call(5)
# here parentheses are still required since
x = method(:puts).call(5)
# will still assign the result of the puts call to x
The first link you provided talking about operators having a higher precedence than method calls is talking about method arguments.
puts 5 + 5
# can be seen as
(puts 5) + 5
# or
puts (5 + 5)
In this case 10 is printed since the operators have higher precedence than the method call itself. This also works for the = operator, but when used as argument.
puts x = 5
Will print 5, return nil and have 5 assigned to x. When using x = puts 5, x can't be assigned without evaluating puts 5 so that is what happens first. Precedence only comes into play if the same code could be executed in multiple ways.
Calling methods with parentheses never yields the above issue.
puts(5 + 5)
# or
puts(5) + 5
Both speak for themself. Although the latter will raise a NoMethodError.

If modifier of Ruby is different than other languages

Its very simple question but I am at beginning level and little bit confused from if modifier of Ruby, i search on Google but not clear yet how it is different than other programming languages like java etc.
can anyone make me understand with simple example or refer to useful blog please.
Thanks in advance. :)
Favor modifier if/unless usage when you have a single-line body. Like :
number = 4
puts "number is even" if number.even?
# >> "number is even"
If you have more than one line of logic, then use the traditional way to write it :
number = 4
if number.even?
# some work with number then print it
puts "number is even"
end
# >> "number is even"
expr if expr
executes left hand side expression, if right hand side expression is true.
There is a gotcha with using the if modifier, which could be a source of bugs. The if modifier has very low precedence and binds more loosely than the assignment operator. That is, the assignment expression will supersede the modifier expression.
If x does not have a method named foo, then nothing happens at all, and the value of y is not modified.
y = x.foo if x.respond_to? :foo
In the second line, the if modifier applies only to the method call. If x does not have a foo method, then the modified expression evaluates to nil, and this is the value that is assigned to y.
y = (x.foo if x.respond_to? :foo)
This really could trip up your program. Again, y is not modified in first example. y is assigned value nil in second example:
y = x.foo if x.respond_to? :foo
y = (x.foo if x.respond_to? :foo)

Break in While Loop when Specific Input is Entered

I am trying to halt input from a user when their input is 42.
The correct answer on the website I'm working on is:
while line = gets
break if (/42/ =~ line)
x << line
end
The code I tried that does not work is:
while line = gets.chomp
break if (line == 42)
x << line
end
Why is this the case? Am I missing some limitations to what I can use in my if statement?
The problem is that 42 is an integer, but line is a string:
1.9.3p392 :001 > "42" == 42
=> false
So it's never the case that your if statement is getting triggered, because it's comparing two different kinds of things. Matching with a Regex fixes it, though it's looking for "42" to appear anywhere in the input (e.g. "3427"). I think what you meant to say was
while line = gets.chomp
break if (line == "42")
x << line 
end
In other words, break when the input is a string with the characters 4 and 2 in it.
I suspect it's because you're comparing a number to a string. The example uses a regular expression it appears. "42" == 42 will give you false in ruby.
<< is a method(Append) on Array or String class objects. But your x not holding any referencing to such objects. Thus you are getting undefined local variable or method 'x' for main:Object (NameError).
Try this instead(by fixing local variable x to hold a practical object and converting line value to Fixnum object:
x = "hi"
while line = gets.chomp
break if (line.to_i == 42)
x << line
end
This program will help you to halt input from a user when their input is 42.
until (line = gets.chomp).to_i == 42
x << line
end
This of course bypasses the if statement you were asking about.
Your limitation for the if is based solely on the fact that you are comparing a string that will always be a string to a number, and this will never be equal. (as others have mentioned)
So we must reconsider the conditional statement. In this case, I considered it "out of place" and moved the comparison to the 'while' loop, and then inverted it to an 'until' statement, to be able to positively express the condition to end the loop on. Whenever I see a 'break' in a loop, I try to get rid of that smell, as the condition to leave a loop should really be expressed in the loop condition if possible.
I hope this helps.

Shorthand for "return x if x" in Ruby

One thing I love about Ruby is that you can express things in the shortest way possible.
I know that one can do, when assigning
x ||= a
# instead of
x = a unless x
# which is
x = x || a
Is there an analog form for return?
# instead of
return x if x
I'm trying to "say" x only once. This question asks about just returning (nothing), but I don't see how to do it when returning something other than void.
I'm just about certain that there exists no shorthand for your second example—nor could one be written without modifying the Ruby syntax—since it's not a common enough idiom. Sorry, bro, but it looks like you're going to have to be verbose on this one. (Though, really, as far as verbosity goes, this one isn't all that bad.)
(Note, too, that the first example isn't quite right: x ||= a is equivalent to x = x || a, which can also be expressed as x = a unless x.)
you can omit the return if it is the last statement in a block code.
example
irb(main):002:0> def b(c)
irb(main):003:1> c if c
irb(main):004:1> end
=> nil
irb(main):005:0> b(4)
=> 4
irb(main):006:0> b(nil)
=> nil
irb(main):007:0> b(true)
=> true
irb(main):008:0> b(false) # TADA!!!
=> nil

Resources