Why is this not a syntax error? - ruby

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.

Related

Ruby's && logic behaving inconsistently when RSpec tested

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.

Ruby if statement multiline condition syntax?

Hi all new to ruby is this if statement valid syntax?
if (verify_login_id = Login.where(params[:email_address], "active" =>1).select('id')# => [#<Login id: 767>]
verify_admin_id = Admin.where("login_id" => verify_login_id.first.id).exists? #=> true)
puts "trueee"
else
raise(ActiveRecord::RecordNotFound.new("Authorization is required to access this endpoint!"))
end
Although setting variable values within an if statement is syntactically valid, it is very problematic.
It can be mistakenly read as a comparison rather than assignment, and in many cases it is a result of someone trying to make a comparison and confusing the equals predicate == with the assignment operator =. That's why a lot of IDEs today mark such code as a warning and a probable error.
In your code it also seems quite unneeded... Break it into two more readable lines:
verified_login = Login.where(params[:email_address], "active" =>1).select('id').first # => [#<Login id: 767>]
if verified_login && Admin.where("login_id" => verified_login.id).exists? #=> true
puts "trueee"
else
raise(ActiveRecord::RecordNotFound.new("Authorization is required to access this endpoint!"))
end
Some more observations - your assignment to verify_login_id is not an id, but a Login object, and your assignment to verify_admin_id is not an id either - but a boolean (and you are not using it anyway). This might seem besides the point - but it adds up to an unreadable and an unmaintainable code.
No it is not a valid code. In the second line, it contains an invalid #-comment that has removed a single ) from the code. (exact snip is #=>true) and should be ) #=>true)
I've removed the comments from the code, added the missing parenthesis and the parser seems to accept it. I couldn't run it of course. So, try this one, it might work:
if (verify_login_id = Login.where(params[:email_address], "active" =>1).select('id')
verify_admin_id = Admin.where("login_id" => verify_login_id.first.id).exists?)
puts "trueee"
else
raise(ActiveRecord::RecordNotFound.new("Authorization is required to access this endpoint!"))
end
Regarding multiline condition in the if - yes, it is possible. Sometimes directly, sometimes with a small trick. Try this:
if (1+2 >=
3)
puts "as"
end
It works (at least on Ruby 1.9.3). However, this will not:
if (1+2
>=
3)
puts "as"
end
This is because of some internal specific of how Ruby interpreter/compiler is designed. In the last example, to make it work, you need to inform Ruby that the line has not ended. The following example works:
if (1+2 \
>=
3)
puts "as"
end
Note the \ added at the end of problematic line

Checking if variable is nil or empty in a single line using or operator

I have absolutely no experience in Ruby, but am trying to debug an issue in FPM. Sometimes, when running FPM, I get the following stack trace:
output': undefined method `empty?' for nil:NilClass (NoMethodError)
I traced this error back to this line (rpm.rb, line 419):
if #epoch.nil? or #epoch.empty?
#logger.warn("no value for epoch is set, defaulting to nil")
return nil
end
This blog post says the following about the or operator:
The best way to think about the chains constructed with or is as
series of fallbacks: try this, if that fails try this, and so on
This leads me to believe that in the above code fragment, if epoch.nil? evaluates to true, it will not attempt to check epoch.empty?, but a simple codepad shows that to be wrong (http://codepad.org/qI7rLvBX)
str = nil
puts str.nil? or str.empty?
Output:
Line 2: undefined method `empty?' for nil:NilClass (NoMethodError)
true
if I change it the operator to ||, then the short-circuiting works correctly - if epoch.nil? evaluates to true, it does not attempt to evaluate epoch.empty?.
Am I misunderstanding something about the or operator? And is the way FPM is checking for nil or empty incorrect?
You could skip the whole problem by noting that nil.to_s is an empty string:
NilClass#to_s → ""
Always returns the empty string.
Given that, you could say:
if #epoch.to_s.empty?
This is a useful trick for avoiding a bunch of noisy nil? tests when dealing with strings.
It's a matter of operator precedence. || has a higher precedence than or.
You should write it like:
str = nil
puts (str.nil? or str.empty?)
puts str.nil? || str.empty?
See more in Difference between "or" and || in Ruby?
For the issue of FPM, I have tried under Ruby 1.8.6, 1.9.3, 2.0.0, and did not get error with or expression.

Is !foo = expression legal in Ruby?

Note: I am not exactly sure what to name the question, so if someone has a better idea please edit it.
I will jump right into the question, since there isn't any fore-explaining required.
This code:
!foo = true
generates this warning
warning: found = in conditional, should be ==
I would understand if this was happening after an if or unless statement, but this couldn't be further away from them (exaggerating). I do realise I could use:
foo = true
!foo
I suppose, the warning isn't a big deal, but it is a bit irritating that Ruby is assuming I am doing something wrong—when I am not.
Questions:
Is this a bug?
Can the warning be disabled?
Thanks!
Is legal. Not a bug. The warning can be suppressed.
You can disable the warning with:
$VERBOSE = nil
It's interesting, $VERBOSE is a case where setting something to false does something different than setting it to nil.
By the way, the other answers, at least initially, tend to assume that Ruby parses the expression as
(!foo) = true
... but that's not the case. It is parsed as:
!(foo = true)
... and so it's doing exactly what the OP wanted. And there is no specification or ratified standard for Ruby, so if it works in MRI (the reference implementation) then it's legal.
As previous answers already suggested, that's not a valid thing of doing what you want to do.
!foo = true
evaluates as
!(foo = true)
That is, assign true to foo and get the negation of the result of that assignment, which boils down to
!true
or
false
If you want to store !true, it has to be
foo = !true
If you want to assign true to foo and the negation to another variable, it'd be
foo2 = !(foo = true)
and that will still cause a warning, because after all it is an assignment in a conditional.
I actually want to assign true to foo, and then get the opposite of foo on the stack
Doesn't really make much sense. You "get something on the stack" by assigning it to a variable, like foo2 in my example.
If the purpose here is to assign to an instance variable and return the negation from a method, then yes, you will have to first assign to the variable and then explicitly return the negation. This is not a bug in Ruby but actually a feature, and for the sake of clean code, you shouldn't be doing it in one line because it's basically indistinguishable from the common bug of using = when == was meant.
It's only a warning, and is evaluating as you expect. You can disable warnings temporarily by assigning $VERBOSE=nil.
save_verbose, $VERBOSE = $VERBOSE, nil
result = !foo = true
$VERBOSE = save_verbos
result
Other places on the net, suggest making a helper method ala
module Kernel
def silence_warnings
with_warnings(nil) { yield }
end
def with_warnings(flag)
old_verbose, $VERBOSE = $VERBOSE, flag
yield
ensure
$VERBOSE = old_verbose
end
end unless Kernel.respond_to? :silence_warnings
But, I just tried this in 1.9.2 and 1.8.7 and it was ineffective at suppressing the "warning: found = in conditional, should be =="
That's technically an invalid left hand assignment. You want
foo = !true
You can't assign a value to the opposite of the object. What is not foo? =)
This is a mistake in your code:
!var = anything
Is wrong. You're trying to assign to either TrueClass or FalseClass, which is (probably) what !var returns.
You want:
!var == true
Now you're doing the comparison (albeit an unnecessary one).

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