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.
Related
I have this (I believe) straight and easy method meant to verify if a certain string only includes numbers and isn't empty.
class String
def is_number?
puts "Here it's working, 1"
if self.scan(/\D/).empty? and self != ""
return true
puts "true"
else
return false
puts "false"
end
puts "Here it's working, 2"
end
end
"asd".is_number?
puts "Here it's working, 3"
The result is quite astonishing to me:
The method works until before the conditional. At that point it doesn't go with the "then" nor the "else" options (which, up to today, I never thought to be an option too), and instead breaks the method. Then, it proceeds to the following command. Finally, at the end of the program it sits there without throwing any error.
I honestly don't know how to proceed at this point.
When you used return in a method it will not execute any code after that, if you are expecting true/false to print you should put it above the return statement
def is_number?
puts "Here it's working, 1"
if self.scan(/\D/).empty? and self != ""
puts "true"
return true
else
puts "false"
return false
end
puts "Here it's working, 2"
end
Note:- "Here it's working, 2" statement will never execute as there will be return statement before that.
it doesn't go with the "then" nor the "else" options
No, this is not what happens here, as described in the answer from Salil.
For the future, if you formulate a hypothesis about your code, you should prove or disprove it. Not for us, for yourself. Else how do you know this is actually what is happening?
For example, something like this would reliably verify that the control does indeed enter one of the conditional branches.
if self.scan(/\D/).empty? and self != ""
#return true
#puts "true"
raise "error from if branch"
else
#return false
#puts "false"
raise "error from else branch"
end
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
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
The following conditional syntax displays the string 'is true' in irb without using puts
irb(main):001:0> if true
irb(main):002:1> 'is true'
irb(main):003:1> else
irb(main):004:1* 'is false'
irb(main):005:1> end
=> "is true"
...yet when I invoke the same syntax in a script and run it from the command line, it gets ignored. Why?
# Odd behaviour:
puts "Why do only two of the three conditionals print?"
# This doesn't put anything to screen:
if true
'is true_1'
else
'is false'
end
puts "Seriously, why? Or better yet: how?"
# But this does:
if true
puts 'is true_2'
else
puts 'is false'
end
# And this works without "puts":
def truthiness
if 1.send(:==, 1)
'is true_3'
else
'is false'
end
end
puts truthiness
puts "Weird."
When I run this as a script, it displays:
"Why do only two of the three conditionals print?
Seriously, why? Or better yet: how?
is true_2
is true_3
Weird."
FWIW, I am following along with Sandi Metz's talk "Nothing is Something"
https://youtu.be/zc9OvLzS9mU
...and listening to this:
https://youtu.be/AULOC--qUOI
Apologies as I am new to Ruby and trying to wrap my head around how it does what it does.
EDIT:
Useful resources:
http://ruby-doc.org/core-2.3.1/Kernel.html#method-i-puts
https://softwareengineering.stackexchange.com/questions/150824/is-the-puts-function-of-ruby-a-method-of-an-object
The IRB output here is showing the return value of the operation, which is not necessarily what is printed to STDOUT (i.e. the terminal) during execution.
Your script is just throwing the return value away, you would have to do this:
val = if true
'is true_1'
else
'is false'
end
puts val
This is going to sound weird, but I would love to do something like this:
case cool_hash
when cool_hash[:target] == "bullseye" then do_something_awesome
when cool_hash[:target] == "2 pointer" then do_something_less_awesome
when cool_hash[:crazy_option] == true then unleash_the_crazy_stuff
else raise "Hell"
end
Ideally, I wouldn't even need to reference the has again since it's what the case statement is about. If I only wanted to use one option then I would "case cool_hash[:that_option]", but I'd like to use any number of options. Also, I know case statements in Ruby only evaluate the first true conditional block, is there a way to override this to evaluate every block that's true unless there is a break?
You could also use a lambda:
case cool_hash
when -> (h) { h[:key] == 'something' }
puts 'something'
else
puts 'something else'
end
Your code is very close to being valid ruby code. Just remove the variable name on the first line, changing it to be:
case
However, there is no way to override the case statement to evaluate multiple blocks. I think what you want is to use if statements. Instead of a break, you use return to jump out of the method.
def do_stuff(cool_hash)
did_stuff = false
if cool_hash[:target] == "bullseye"
do_something_awesome
did_stuff = true
end
if cool_hash[:target] == "2 pointer"
do_something_less_awesome
return # for example
end
if cool_hash[:crazy_option] == true
unleash_the_crazy_stuff
did_stuff = true
end
raise "hell" unless did_stuff
end
I think, following is the better way to do the stuff you want.
def do_awesome_stuff(cool_hash)
case cool_hash[:target]
when "bullseye"
do_something_awesome
when "2 pointer"
do_something_less_awesome
else
if cool_hash[:crazy_option]
unleash_the_crazy_stuff
else
raise "Hell"
end
end
end
Even in case's else part you can use 'case cool_hash[:crazy_option]' instead of 'if' if there are more conditions. I prefer you to use 'if' in this case because there is only one condition.
in ruby 3.0 you can do the following with pattern matching
# assuming you have these methods, ruby 3 syntax
def do_something_awesome = "something awesome 😎"
def do_something_less_awesome = "something LESS awesome"
def unleash_the_crazy_stuff = "UNLEASH the crazy stuff 🤪"
you can do
def do_the_thing(cool_hash)
case cool_hash
in target: "bullseye" then do_something_awesome
in target: "2 pointer" then do_something_less_awesome
in crazy_option: true then unleash_the_crazy_stuff
else raise "Hell"
end
end
will return
do_the_thing(target: "bullseye")
=> "something awesome 😎"
do_the_thing(target: "2 pointer")
=> "something LESS awesome"
do_the_thing(crazy_option: true)
=> "UNLEASH the crazy stuff 🤪"
in ruby 2.7 it still works
# need to define the methods differently
def do_something_awesome; "something awesome 😎"; end
def do_something_less_awesome; "something LESS awesome"; end
def unleash_the_crazy_stuff; "UNLEASH the crazy stuff 🤪"; end
# and when calling the code above to do the switch statement
# you will get the following warning
warning: Pattern matching is experimental, and the behavior may change
in future versions of Ruby!