What's the difference between these two Ruby if statements when we put a then at the end of the if statement?
if(val == "hi") then
something.meth("hello")
else
something.meth("right")
end
and
if(val == "hi")
something.meth("hello")
else
something.meth("right")
end
then is a delimiter to help Ruby identify the condition and the true-part of the expression.
if condition then true-part else false-part end
then is optional unless you want to write an if expression in one line. For an if-else-end spanning multiple lines the newline acts as a delimiter to split the conditional from the true-part
# can't use newline as delimiter, need keywords
puts if (val == 1) then '1' else 'Not 1' end
# can use newline as delimiter
puts if (val == 1)
'1'
else
'Not 1'
end
Here's a quick tip that is not directly related to your question: in Ruby, there is no such thing as an if statement. In fact, in Ruby, there are no statements at all. Everything is an expression. An if expression returns the value of the last expression that was evaluated in the branch that was taken.
So, there is no need to write
if condition
foo(something)
else
foo(something_else)
end
This would better be written as
foo(
if condition
something
else
something_else
end
)
Or as a one-liner
foo(if condition then something else something_else end)
In your example:
something.meth(if val == 'hi' then 'hello' else 'right' end)
Note: Ruby also has a ternary operator (condition ? then_branch : else_branch) but that is completely unnecessary and should be avoided. The only reason why the ternary operator is needed in languages like C is because in C if is a statement and thus cannot return a value. You need the ternary operator, because it is an expression and is the only way to return a value from a conditional. But in Ruby, if is already an expression, so there is really no need for a ternary operator.
The then is only required if you want to write the if expression on one line:
if val == "hi" then something.meth("hello")
else something.meth("right")
end
That brackets in your example are not significant, you can skip them in either case.
See the Pickaxe Book for details.
The only time that I like to use then on a multi-line if/else (yes, I know it's not required) is when there are multiple conditions for the if, like so:
if some_thing? ||
(some_other_thing? && this_thing_too?) ||
or_even_this_thing_right_here?
then
some_record.do_something_awesome!
end
I find it to be much more readable than either of these (completely valid) options:
if some_thing? || (some_other_thing? && this_thing_too?) || or_even_this_thing_right_here?
some_record.do_something_awesome!
end
# or
if some_thing? ||
(some_other_thing? && this_thing_too?) ||
or_even_this_thing_right_here?
some_record.do_something_awesome!
end
Because it provides a visual delineation between the condition(s) of the if and the block to execute if the condition(s) evaluates to true.
There's no difference at all.
And, just FYI, your code can be optimized to
something.meth( val == 'hi' ? 'hello' : 'right' )
Related
Ruby newbie here. I've written a case statement to check whether a string parameter ends with "?", and I don't understand the result. Here's my code:
class Bob
def self.hey(phrase)
case phrase
when phrase.chars.last == "?"
'Sure.'
else
'Whatever.'
end
end
end
When I call Bob.hey("Does this cryogenic chamber make me look fat?") I get back Whatever., despite "Does this cryogenic chamber make me look fat?".chars.last == "?" evaluating to true in IRB. I can't figure out what I'm missing. Any help would be appreciated.
There's two forms of the case statement, one where you specify case expr and another where you don't. In the first form the expr value is tested against all of the branches with ===. In the second form each branch is evaluated like an if would be.
This means there's two ways to fix this. Either drop the term from the case part:
def self.hey(phrase)
case
when phrase.chars.last == "?"
'Sure.'
else
'Whatever.'
end
end
Or switch the case to focus on the important part:
def self.hey(phrase)
case phrase.chars.last
when "?"
'Sure.'
else
'Whatever.'
end
end
Another way to do this is to use a regular expression:
def self.hey(phrase)
case phrase
when /\?\z/
'Sure.'
else
'Whatever.'
end
end
Where /\?\z/ means "question mark character at end of string.
If there are only two cases, an if/else is more than enough:
if phrase.chars.last == "?"
...
else
...
end
Note that you could use end_with? :
if phrase.end_with?('?')
...
I'm fairly new to code and I have a quick question on Ruby conditionals, specifically Case Expressions. I have a method in which I want to return a string "odd" if the string length is odd and "even" if the string length is even.
Simple stuff I know and I can get the results using a if/else conditional, however the case expression just returns 'nil'. Any help would be greatly appreciated.
def odd_or_even(string)
case string
when string.length.even? then "even"
when string.length.odd? then "odd"
end
end
odd_or_even("Ruby") # Wanting to return even rather than nil
odd_or_even("Rails") # Wanting to return odd rather than nil
You've written your case statement wrong. It takes two forms, which is unusual compared to other languages. The first form takes an argument, and that argument is compared to all possible cases. The second form is without argument and each case is evaluated independently.
The most minimal fix is this:
def odd_or_even(string)
case
when string.length.even? then "even"
when string.length.odd? then "odd"
end
end
This was because to Ruby your code looked like this when calling with the argument "Ruby":
def odd_or_even(string)
case string
when true then "even"
when false then "odd"
end
end
Your string value does not match true or false so you get nil from a non-matching situation.
You can clean up your code considerably. Consider: Can something be not even and not odd? Not really:
def odd_or_even(string)
string.length.even? ? 'even' : 'odd'
end
case something
when condition1
expression1
when condition2
expression2
else
default_expression
end
is equivalent to
if condition1 === something
expression1
elsif condition2 === something
expression2
else
default_expression
end
Note that case-when internally uses ===, an operator (method) which can be overridden.
statement 1:
[2,4,6,7,8].each do |i|
(i % 2 == 0) || (puts "Not even" && break)
puts i
end
statement 2:
[2,4,6,7,8].each do |i|
(i % 2 == 0) || (puts("not even") && break)
puts i
end
Statement 1 breaks but doesn't put, and statement 2 puts but doesn't break
Why does it behave this way, and how could I write this expression better? (I don't mean finding odd #s, I mean || && statements)
Statement 1:
Ruby interprets it like this: puts("Not even" && break).
So break is executed before puts argument is parsed.
Statement 2:
puts("not even") returns nil and so break is not executed at all
If you realy want it to be inline I'd suggest writing this without &&: (puts "Not even"; break)
how could I write this expression better
Don't [write expressions where you mean to use flow-control statements].
Using boolean expressions for flow-control like that is fragile at best. And, more likely, is just confusing and hard to understand. Compare to this, for example.
[2,4,6,7,8].each do |i|
if i % 2 != 0 # if i.odd?
puts 'not even'
break
end
puts i
end
Which style to choose, depends on what you're after: readability or terseness. If you're not participating in code golf, I'd say, choose readability every time.
puts "Not even" && break is equal to puts("Not even" && break), break will be executed, so the loop will break.
puts("not even") will return nil.
puts("not even") && break is equal to: nil && break, break isn't executed, so the loop doesn't break.
The reason is that function application has a lower precedence than any operators in Ruby.
So
puts "Not even" && break
becomes
puts ("Not even" && break)
(the && is evaluated before the function call). See also Ruby operator precedence
I have a function similar to the following:
def check
return 2 == 2 || 3 != 2 || 4 != 5
end
My question is, will Ruby perform all the comparisons even though the first is true, and thus the function return true. My checks are much more intensive, so I'd like to know if I should break this out in a different way to avoid making all the checks every time.
irb(main):004:0> 2 == 2 || 3 != 2 || 4 != 5
=> true
Thank you.
Ruby uses short-circuit evaluation.
This applies to both || and &&.
With || the right operand is not evaluated if the left operand is truthy.
With && the right operand is not evaluated if the left operand is falsy.
|| short-circuits as soon as the first condition is true. So yes, it will help if you put the most expensive conditions at the end.
|| will by default short-circuit evaluate, meaning that once the first "true" expression is encountered it will stop evaluation (unless you explicitly state you want all expressions to evaluate with the 'or' operator).
reference:
http://en.wikipedia.org/wiki/Short-circuit_evaluation
As soon as one of the condition is true, the function will return.
You can test it yourself in irb, like this:
irb> p('Hello') || p('World')
As we know the function p prints its parameters(in an inspect manner) then returns them, so if the || short circuits, only "Hello" is printed, otherwise both "Hello" and "World" are printed.
You can also test the logical && operator, by using puts instead of p, as puts always returns nil.
BTW, irb is a perfect place to play around ruby. You can test everything there, except a small portion of concurrency.
Is there a ruby idiom for "If do-this," and "do-this" just as a simple command?
for example, I'm currently doing
object.method ? a.action : nil
to leave the else clause empty, but I feel like there's probably a more idiomatic way of doing this that doesn't involve having to specify a nil at the end. (and alternatively, I feel like taking up multiple lines of code would be wasteful in this case.
As a general rule: you pretty much never need the ternary operator in Ruby. The reason why you need it in C, is because in C if is a statement, so if you want to return a value you have to use the ternary operator, which is an expression.
In Ruby, everything is an expression, there are no statements, which makes the ternary operator pretty much superfluous. You can always replace
cond ? then_branch : else_branch
with
if cond then then_branch else else_branch end
So, in your example:
object.method ? a.action : nil
is equivalent to
if object.method then a.action end
which as #Greg Campbell points out is in turn equivalent to the trailing if modifier form
a.action if object.method
Also, since the boolean operators in Ruby not just return true or false, but the value of the last evaluated expression, you can use them for control flow. This is an idiom imported from Perl, and would look like this:
object.method and a.action
a.action if object.method?
Greg's answer is the best, but for the record, and even more than in C, expressions and statements are equivalent in Ruby, so besides a.action if o.m? you can also do things like:
object.method? && a.action
You can write (a; b; c) if d or even
(a
b
c
) if d
or for that matter: (x; y; z) ? (a; b c) : (d; e; f)
There is no situation in Ruby where only a single statement or expression is allowed...
result = (<expression> && <true value>) || <false value>
value = 1
result = (value == 1 && 'one' ) || 'two'
result #=> 'one'
Explain: value == 1 && 'one' #=> returns last expression result, value is equals 1 so and section will be evaluated, and return 'one'.
value = 0
result = (value == 1 && 'one' ) || 'two'
result #=> 'two'
Explain: value != 1 and 'and' expression will not be evaluated, but instad will be used 'or' expression and it returns 'two'
Another way this can be done on the same line is:
if object.method; a.action end
This is considered bad style by Rubocop because it uses a semicolon to terminate the expression, but I find it more readable in some conditions than tacking on the if statement at the end. It is easier to overlook an if statement at the end and I don't always want to return something if the condition isn't true(as you are forced into with a ternary operator).
You can also be a bit more verbose and rubocop friendly:
if object.method then a.action end