if-elsif without conditions not branching correctly in Ruby - ruby

When I run the following code:
if
puts "A"
elsif
puts "B"
end
I get the output:
A
B
Why does it not warn or raise any errors? And why does it execute both branches?

an if-elsif without conditions
Here's where you're wrong. The puts are the conditions. There are no bodies in that snippet, only the conditions.
Here's your code, properly formatted.
if puts "A"
elsif puts "B"
end
And why it executes both branches?
puts returns nil, a falsey value. That's why it tries both branches. If this code had an else, it'd be executed too.

In other words :
if # this is the condition :
puts "A" # an expression which prints A and returns nil
# hence it's like "if false", try elsif ...
then
puts 'passes in then'
elsif # this is another condition :
puts "B" # puts prints B and returns nil
else # no condition satisfied, passes in else :
puts 'in else'
end
Execution :
$ ruby -w t.rb
A
B
in else

Related

include works with if but not else

I doing a Ruby botcamp. I'm supposed to write code that replaces all user input of 's' with 'th' so it reads like Daffy Duck is speaking. If I enter an s it will be replaced with th. That works! But If I don't enter an 's' it's supposed to print that none were included in my elsif statemnt. Instead I'm getting the error 'undefined method `include?' for nil:NilClass'. Other than that error, the interpretor is telling me the code is good.
print "Input a string: "
user_input=gets.chomp.downcase!
if user_input.include?"s"
user_input.gsub!(/s/, "th")
puts "Your string is #{user_input}!"
elsif
puts "There are no s's in your string!"
end
Any ideas on what I need to change?
You need to be careful with built-in ruby methods that end with an exclamation point (!). A lot of them will return nil if no changes were made:
'test'.downcase! # => nil
'Test'.downcase! # => "test"
Since you are assigning the result to a variable, there's no need to use the exclamation point method, since those modify in-place, you can just use the normal downcase method.
'test'.downcase # => "test"
You also later on have an elsif with no condition, that should probably just be an else. It's actually executing the first line of the "body" of the elsif as the conditional:
if false
puts "a"
elsif
puts "b" # recall that `puts` returns `nil`
puts "c"
else
puts "d"
end
This results in
b
d
being output

Calling method isn't returning string

I created a method to count a substring 'e' in a string passed as an argument. If there isn't a substring 'e' in the string, it should return "There is no \"e\"." I am trying to achieve this:
How many times 'e' is in a string.
If given string doesn't contain any "e", return "There is no "e"."
if given string is empty, return empty string.
if given string is nil, return nil.
This is my code:
def find_e(s)
if !s.include?("e")
"There is no \"e\"."
elsif s.empty?
""
else s.nil?
nil
end
s.count("e").to_s
end
find_e("Bnjamin")
It skips the if statement and it still uses the method count. Why is this?
To achieve what you want you could move your string.count to the else statement in your if, because actually you're making your method return the quantity of e passed in the count method over your string, but what happens inside the if isn't being used:
def find_e(s)
if s.nil?
nil
elsif s.empty?
''
elsif !s.include?("e")
"There is no \"e\"."
else
s.count("e").to_s
end
end
p find_e("Bnjamin") # => "There is no \"e\"."
p find_e("Benjamin") # => "1"
p find_e(nil) # => nil
p find_e('') # => ""
And also your validations must be in order, first check nil values, then empty values, and then the rest, if you don't then you'll get some undefined method ___ for nil:NilClass errors.
You might have a hard time using the method you wrote. In the next method, you'll need a new case statement to test if find_e returned nil, an empty string, a string with a number or "no e".
This method would be a bit more consistent:
def count_e(string_or_nil)
count = string_or_nil.to_s.count("e")
if count == 0
"There is no \"e\"."
else
count
end
end
puts count_e("Covfefe")
# 2
puts count_e("Bnjamin")
# There is no "e".
puts count_e("")
# There is no "e".
puts count_e(nil)
# There is no "e".
But really, if there's no e in the input, just returning 0 would be the most logical behaviour.
You need to put your count method in a branch of the if/else statement, or else it will be evaluated last every time. Without an explicit return statement Ruby will return the last statement, so putting the method outside the if/else branch on the last line guarantees it will always be hit. Also, nil can be converted to an empty string by calling #to_s, so you can remove one of your branches by converting s.to_s, calling empty? and returning s
def find_e(s)
if s.to_s.empty?
s
elsif !s.include?("e")
"There is no \"e\"."
else
s.count("e").to_s
end
end
If you just return 0 whether you get nil, an empty string, or a string without e, you can make it one line
def find_e(s)
s.to_s.count("e").to_s
end
If it were me I'd probably return an Integer, which can always be converted to a String later. puts and "#{}" will implicitly call to_s for you anway. Then you can use that integer return in your presentation logic.
def count_e(input)
input.to_s.count("e")
end
def check_for_e(input)
count = count_e(input)
count > 0 ? count.to_s : "There's no \"e\"."
end
check_for_e("Covfefe") # => "2"
check_for_e("Bnjamin") # => "There's no \"e\"."
check_for_e(nil) # => "There's no \"e\"."
check_for_e("") # => "There's no \"e\"."
In Ruby, methods return the last statement in their body. Your method's last statement is always s.count("e").to_s, since that lies outside of the if statements.

Return few lines back if condition is true

Let's say that I have this simple if-elsif-else block of code.
def
# some code...
input = get.chomp
if input == 1
puts "foo"
elsif input == 2
puts "bar"
else
# exit
end
# some more code...
end
How do I tell program to go back and ask for input again for cases 1 and 2 and continue with code within this method if else is triggered? I do not want to go back to start of the method, instead I just want to back to input variable declaration.
def
# some code...
loop do
input = get.chomp
if input == 1
puts "foo"
break
elsif input == 2
puts "bar"
break
end
end
# some more code...
end
Note: Your two if/elsif conditions will never be satisfied.
# main procedure
# defined here so other functions could be declared after
# the main procedure is called at the bottom
def main
loop do
puts "Insert a number"
input = gets.chomp.to_i
if isValidInput input
puts case input
when 1
"foo"
when 2
"bar"
end
break
end
end #loop
puts "Other code would execute here"
end
# Validity Checker
# makes sure your input meets your condition
def isValidInput(input)
if [1,2].include? input
return true
end
return false
end
main

Strange result in Ruby with "end if"

I expect this code to execute the code block and result in the output "x" and "y", or just to throw a syntax error:
if true
puts "x"
end if
puts "y"
However, the interpreter ignores the if true block and only executes puts "y". If I instead enter the following code:
if true
puts "x"
end if
the interpreter exits with an end-of-input syntax error. Is there a reason why the first snippet is valid code but somehow executing wrong? It would seem to me that there is some error in the parser.
I've confirmed this in Ruby 2.1.2 as well as Ruby 2.1.5.
There are two things playing together here:
The return value of the puts is nil
Ruby is usually clever enough to read the next line if the current command hasn't ended yet.
That means:
if true
puts "x"
end if
puts "y"
is the same than:
if true
puts "x"
end if (puts "y")
Ruby evaluates puts "y" to nil:
if true
puts "x"
end if nil
What leads Ruby to not evaluate the if true block, because if nil acts like if false.
Or in other words: Your example is the same as:
if puts("y") # evaluates to nil (aka is falsey)
if true
puts "x"
end
end
I think what you mean to write is
if true
puts "x"
end
puts "y"
That would produce the output you expect.
Your code is incorrect. The correct code is:
if true
puts "x"
end
puts "y"
Your code tells Ruby to execute the if true ... end block if puts "y" returns true.
puts returns nil, which amounts to false in a condition check, leading to the block not being executed at all.
Your code is effectively saying only to execute the if true block only if puts "y" returns true. Unfortunately, puts returns nil. To end an if statement in Ruby, you simple have to use end. Unlike in Shell Scripting or Visual Basic, there is no specific end statements for different blocks.
Change
if true
puts "x"
end if
puts "y"
to
if true
puts "x"
end
puts "y"
And you'll be golden.

How is the value of a begin block determined?

According to The Ruby Programming Language p.164.
If a begin statement doesn't propagate an exception, then the value
of the statement is the value of the last expression evaluated in
the begin, rescue or else clauses.
But I found this behavior consistent with the begin block together with else clause and ensure clause.
Here is the example code:
def fact (n)
raise "bad argument" if n.to_i < 1
end
value = begin
fact (1)
rescue RuntimeError => e
p e.message
else
p "I am in the else statement"
ensure
p "I will be always executed"
p "The END of begin block"
end
p value
The output is:
"I am in the else statement"
"I will be always executed"
"The END of begin block"
"I am in the else statement"
[Finished]
The value is evaluated to the else clause. This is inconsistent behavior as the ensure clause is the last statement executed.
Could someone explain what's happening within the begin block?
I'd interpret the goal of the begin/rescue/else/end block as:
Execute the code in the begin section, and then the code in the else section.
If something goes wrong in the begin section, execute the rescue section instead of the else section.
So either the rescue section or the else section will be executed after trying the begin section; so it makes sense that one of them will be used as the whole block's value.
It's simply a side effect that the ensure section will always be executed.
val = begin
p "first"; "first"
rescue => e
p "fail"; "fail"
else
p "else"; "else"
ensure
p "ensure"; "ensure"
end
val # => "else"
# >> "first"
# >> "else"
# >> "ensure"
But:
val = begin
p "first"; "first"
raise
rescue => e
p "fail"; "fail"
else
p "else"; "else"
ensure
p "ensure"; "ensure"
end
val # => "fail"
# >> "first"
# >> "fail"
# >> "ensure"
I'm just guessing here, but as the purpose of a ensure block is to finalize any resources that may remain open (cleanup in other words), and so it makes sense that the logical value should be the result of the else statement. It makes sense to me that it is by design.
In this case the begin block is just a way of defining a section for which you may want to do exception handling.
Remember that else in this case runs if no exceptions occur, and ensure will run regardless of exceptions or a lack thereof.

Resources