Ruby's && logic behaving inconsistently when RSpec tested - ruby

I have a method that includes (among subsequent lines) the following code:
candidate_lines.each do |line|
return line if priority_1?(line) && line.marked_by?(self)
end
The methods in it, as far as I can tell, are irrelevant. What's driving me up the wall, is I have a test that should be skipping that part of the method simply by the specification I've given:
it "without any priority 1 lines, returns all priority 2 lines marked by itself" do
line1 = double :line, marked_by?: true
line2 = double :line, marked_by?: true
allow(joshua).to receive(:priority_1?).and_return(:false)
expect(joshua).to receive(:candidate_lines).and_return([line1, line2])
expect(joshua.tiebreak_lines).to eq [line1, line2]
end
Yet when I run the test, the return statement is getting triggered. I've tested it manually with puts statements immediately before the 'return line...' line, and it turns out that, while 'priority_1?(line)' returns false, for some reason 'priority_1?(line) && line.marked_by?(self)' is evaluating to true.
What's happening here?

You're confusing the symbol :false with the boolean value false
Because :false is neither false nor nil, it is truthy, that is as far as things like && or if are concerned, it is true.

Related

What happens with further conditions if first one is `false`?

I often face the situation when if condition A (for example !#object.nil?) is false, condition B, when checked, can raise an error (for example #object.some_method -> undefined method 'some_method' for nil:NilClass).
I tested it in console, but could't get some full data.
1) Is it safe to use and/&& when if first conditional is false, second can lead into an error?
2) What about or?
!#object.nil? && #object.some_method
#object && #object.some_method
Both of the above are valid patterns in Ruby. You will not get the "undefined method for nil" error, because the expression to the right of the && is never evaluated if the expression to the left of it evaluates to nil (or false).
You can also accomplish this check using postfix if or unless:
#object.some_method if #object
or
#object.some_method unless #object.nil?
are both valid alternatives.
Regarding or:
Using || (Rubyists generally avoid the keyword or operator) will not protect you from the error, because when the expression on the left side of the || evaluates to nil, the interpreter will actually continue on and attempt to evaluate the expression on the right side.
Second part of these lines will never be exected:
false && raise "this will never be raised"
true || raise "this will never be raised"
In your case, if you are using active support, you can do:
#object.try!(:some_method)

Why does Ruby expression with double ampersand using a return statement cause a syntax error

def foo
true && return false
end
def bar
true and return false
end
foo method causes a syntax error. bar doesn't. Why?
Assuming I want to evaluate one-liners with a return statement (similar to a certain commonly used shell/bash expression), would this be a recommended way to do it, or is there a more recommended approach?
By operator associativity strength,
true && return false
evaluates to
(true && return) false
of which
true && return
is valid, but the false after it is invalid. You cannot have two statements lined up without anything in between.
Side Note
It is worth noting that and and && are not equivalent.
and is a flow control operator while && is a Boolean operator. What is the difference?
One example of the differences, using and, you can assign values.
value = nil and value / 42
This would fail if you tried it as below
value = nil && value / 42
Original Question 1
Assuming I want to evaluate one-liners with a return statement (similar to a certain > commonly used shell/bash expression), would this be a recommended way to do it, or is there > a more recommended approach?
The way I have always seen this done in Ruby is this:
value if conditional
This will return the value if the conditional is met and nil otherwise. No need for the return statement if this is the last command in the function!
If you are using this for an early exit to a function, I prefer using an unless. For instance...
return unless arguments_are_valid
Original Question 2
foo method causes a syntax error. bar doesn't. Why?
It's a matter of operator precedence. See the example below showing how they are evaluated.
(true && return) false
(true) and (return false)
Because of the && operator precedence, the following line
true && return false
evaluates as
(true && return) false
that does not makes sense. In order to execute your code you need to use
def foo
true && (return false)
end
and doesn't suffer of the same issue because and has lower precedence than &&.
if there is need for shorten statements use
def foo
return false if true
end
def bar
return false if true
end
return is not a function :) therefore it doesn't make sense to say
when true and return is ok send a false

what does nil mean/represent here?

Following are the simple statements in the irb shell. What does nilin the output mean ? Why does it accompany the print statement in the if block ?
irb(main):062:0> if(x==20 && y==30)
irb(main):063:1> print("if statement working !")
irb(main):064:1> else
irb(main):065:1* print("else statement working !!")
irb(main):066:1> end
if statement working !=> nil # what does nil represent here ?
In Ruby, all expressions return values, even if it's nil. Blocks and methods simply return the value of the last expression evaluated. There are many ways to use this effectively. For example, this is the reason explicit returns are often not used. Also, you can do this:
print if x == 20 && y == 30
'if statement working!'
else
'else statement working!'
end
Regarding your example: in addition to printing the string as you instructed, irb will display the value it received from the if-else blocks. Since print always returns nil, both branches will return the same value.
It means that your if-block does not return a value (which it can, actually). For instance, the following is perfectly legal and viable:
foo = if bar > 10
42
else
0
end
# now, foo is either 42 or 0

Why is this not a syntax error?

If I do this:
(false true)
it fails with a syntax error, as I expect. But if I do this:
(false
true)
the code is executed, and it throws away the first condition and returns the result of the second.
Is this considered a bug or a feature?
Line endings are optional, so in this case, the return is causing the parser to interpret it as the following:
(false; true)
which evaluates to just:
(true)
If these were method calls then both would be evaluated, but only the last would be emitted. For example:
x = (p "hello"
p "world"
2)
This will output "hello" and "world" and x will equal 2
Parentheses are used for grouping, line breaks are used as expression separators. So, what you have here is simply a group of two expressions. There is nothing to reject.
This is useful because of this well-known idiom:
def foo(bar = (bar_set = true; :baz))
if bar_set
# optional argument was supplied
end
end
There is simply no other way in Ruby to figure out whether an optional argument was supplied or not.
Basically, this becomes interesting in the presence of side effects, such as assigning a variable in my example or printing to the screen in #32bitkid's example. In your example, there is no side effect, that's why you couldn't see what was actually going on.
Ruby can give you a warning if you have warnings on.
$VERBOSE = true
def foo
(false
true)
end
gives you
(irb):3: warning: unused literal ignored
In both Ruby 1.9.1 patchlevel 378 and Ruby 1.8.7 patchlevel 330.
See How can I run all Ruby scripts with warnings? for how to run all Ruby scripts with warnings on.

Why does a return statement break the conditional operator?

Experimenting with the conditional operator in ruby,
def nada
false ? true : nil
end
def err
false ? true : raise('false')
end
work as expected but
def reflection
false ? true : return false
end
produces a syntax error, unexpected keyword_false, expecting keyword_end
def reflection
false ? true : return(false)
end
and attempted with brackets syntax error, unexpected tLPAREN, expecting keyword_end
yet
def reflection
false ? true : (return false)
end
works as expected, and the more verbose if...then...else...end
def falsy
if false then true else return false end
end
also works as expected.
So what's up with the conditional (ternary) operator?
You can use it like this, by putting the entire return expression in parentheses:
def reflection
false ? true : (return false)
end
Of course, it does not make much sense used like this, but since you're experimenting (good!), the above works! The error is because of the way the Ruby grammar works I suppose - it expects a certain structure to form a valid expression.
UPDATE
Quoting some information from a draft specification:
An expression is a program construct which make up a statement (see 12
). A single expression can be a statement as an expression-statement
(see 12.2).12
NOTE A diļ¬€erence between an expression and a statement is that an
expression is ordinarily used where its value is required, but a
statement is ordinarily used where its value is not necessarily
required. However, there are some exceptions. For example, a
jump-expression (see 11.5.2.4) does not have a value, and the value
of the last statement of a compound-statement can be used.
NB. In the above, jump-expression includes return among others.
I think this is all related to the ruby parser.
ruby parses return as the else-expression of the ternary operator
ruby is then surprised when it finds false instead of end
wrapping return false in parentheses causes ruby to interpret the entire thing as the else-expression
return(false) doesn't work because ruby is still trying to interpret just the return part as the else-expression, and is surprised when it finds a left-parenthesis (updated)
Note: I don't think this is a great answer.
A great answer could, for example, explain the parse errors with reference to the ruby grammar.
The ternery operator is just that, an operator. You don't return from it. You return from functions. When you put a return in an if, you return from the function that the if is in. Since there is no variable awaiting assignment from the result of the if, there is no problem. When you return from the ternery operator, there is no value assigned to the variable.

Resources