Can't understand "unless" keyword in Ruby - ruby

I'm not a native English speaker. I know it sounds stupid nonetheless. I write in Python & C. But I can't quite understand how unless works.
Is it healthy to think of it from a logical standpoint? Eg, consider the if keyword: if condition. If condition is true, the code runs. What's the logical explanation for unless?
Is there any other way to think of it?

unless x is equivalent to if !x

Ruby unless modifier:
Syntax: code unless conditional
Executes code if conditional is false.
Example:
$var = 1
print "1 -- Value is set\n" if $var
print "2 -- Value is set\n" unless $var
$var = false
print "3 -- Value is set\n" unless $var
This will produce following result:
1 -- Value is set
3 -- Value is set

The easiest way is: unless is an opposite of if. It works in the same places (I think) as if:
foo = nil
puts "This will not show" if foo
puts "But this will" unless foo
if foo then
puts "This will not show"
end
unless foo
puts "But this will"
end
It looks nicer in the code if you write unless foo instead of if !foo - but that is only my opinion. In the longer form, you can even use else, but please, do not.
Here is a set of rules to follow to make the code more readable.

The way i think of it is to say:
"The block of code is going to be executed, unless the condition is true"
unless <condition>
<code>
end

hungry = true
unless hungry
puts "I'm writing Ruby programs!"
else
puts "Time to eat!"
end
This is the easy way to understand

Related

Can I recall the "case" in case?

I want to recall the case until user writes a or b. I do not want to use "case"
particularly.
I just want to get input from user but not geting something else. If he writes something else, he should need to write until he writes a or b.
str = gets.chomp.to_s
case str
when "a"
print "nice a"
when "b"
puts "nice b"
else
puts "please do it again"
end
class person
attr_accessor :name , :surname #and other attributes
end
#There will be a method here and it will run when the program is opened.
#The method will create the first object as soon as the program is opened.
#The new object that the user will enter will actually be the 2nd object.
puts "What do you want to do?
add
list
out"
process = gets.chomp.to_s
case process
when "add"
#in here user will add new objects of my class
when "list"
#in here user will show my objects
when "out"
puts "Have a nice day"
else
puts "please do it again"
end
In fact, if you look at it, many actions will be taken as a result of the user entering the correct input. what I want to tell is more detailed in this example. According to the input of the user, there will be actions such as calling methods, adding objects, etc.
I wrote most of the code on my computer. But still I couldn't solve my first problem.
Use Kernel#loop
There are a lot of ways to solve this problem, but let's start with a simple Kernel#loop wrapper around your existing code, as that's probably the easiest path forward for you.
loop do
str = gets.chomp.to_s
case str
when "a"
print "nice a"
when "b"
puts "nice b"
else
puts "please do it again"
# restart your loop when not "a" or "b"
next
end
# exit the loop if else clause wasn't triggered
break
end
Use until Control Expression
The loop construct above is pretty straightforward, but it requires you to think about where you need next and break statements for flow control. My own instinct would be to simply call a block until it's truthy. For example, the core logic could be shortened to:
str = nil; until str =~ /a|b/i do str = gets.chomp end; p str
This is a lot shorter, but it's not particularly user-friendly. To leverage this approach while making the solution more communicative and error-resistant, I'd refactor the original code this way:
# enable single-character input from console
require 'io/console'
# make sure you don't already have a value,
# especially in a REPL like irb
str = nil
until str =~ /a|b/ do
printf "\nLetter (a, b): "
str = STDIN.getch.downcase
end
puts "\nYou entered: #{str}"
While not much shorter than your original code, it handles more edge cases and avoids branching. It also seems less cluttered to me, but that's more a question of style. This approach and its semantic intent also seem more readable to me, but your mileage may legitimately vary.
See Also
IO::Console
Control Expressions
"I just want to do something until something else happens" is when you use some sort of while loop.
You can do this:
while true
str = gets.chomp
break unless str == 'a' || str == 'b'
puts "please do it again"
end
You can also use loop do:
loop do
str = gets.chomp
break unless ['a', 'b'].include?(str)
puts "please do it again"
end
puts "Nice #{str}."
Rubyists tend to prefer loop do over while true. They do pretty much the same thing.
One more thing. There's a simpler way to write out arrays of strings:
loop do
str = gets.chomp
break unless %w(a b).include?(str)
puts "please do it again"
end
puts "Nice #{str}."
It doesn't look a whole lot simpler, but if you have, say, 10 strings, it's definitely quicker to type in when you don't have to use all those quotation marks.
As your intuition was telling you, you don't need to use the case statement at all. Like trying to kill a flea with a sledgehammer. The most concise way to do your check is to check whether the input character is included in an array of the desired characters.

How to convert this if condition to unless?

if array.present?
puts "hello"
end
There is no else part to this.
How to write the above if condition using unless.
I'm asking this question because of this lint error:
Use a guard clause instead of wrapping the code inside a conditional expression
Regarding your comment:
I'm asking this question because of this lint error
Use a guard clause instead of wrapping the code inside a conditional expression
This means that instead of:
def foo(array)
if array.present?
puts "hello"
end
end
You are supposed to use:
def foo(array)
return unless array.present?
puts "hello"
end
See https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals
If this is a Rails question (is it?), you can also use blank?:
def foo(array)
return if array.blank?
puts "hello"
end
There's no reason to.
Remember: unless is the inverse of if (or !if if you rather), and is only intended to make your code easier to read.
Using unless with your expression would be incredibly awkward, because you're now moving the actual body of work to an else statement...
unless array.present?
return
else
puts "hello"
end
...which doesn't make your code any easier to read if you had stuck with a negated if:
if !array.present?
return
else
puts "hello"
end
Don't use unless here. You lose readability in exchange for virtually nothing.
One-liner:
puts "hello" unless !array.present?
However, I would recommend:
puts "hello" if array.present?
unless array.present?
return
else
puts "hello"
end
OP requested one-liner modification:
Pseudocode:
something unless condition
Therefore:
puts "hello" unless !array.present?

How can I make script work in Ruby?

I am new to Ruby.
I need to make this script work:
puts "Do you like cats?"
ask = gets
def ask(n)
if ask == yes
return "I do too"
end
if ask == no
return "Dogs are better"
end
end
puts "#{ask(n)}"
Error message is :
pracif.rb:15:in <main>': undefined local variable or methodn' for
main: Object (NameError)
Here's a script that would work for you :
puts "Do you like cats?"
answer = gets
def ask(n)
if n == 'yes'
return "I do too"
end
if n == 'no'
return "Dogs are better"
end
end
puts ask(answer.downcase.chomp)
Explaination
As the error said you were trying to pass in a variable n which was not defined
Secondly you have a method name ask same as variable name. I've renamed the variable to answer instead
Thirdly, enclose yes and no in quotes
And finally, since you are using gets a \n gets appended like yes\n so none of your conditions would match. So i've used chomp to remove \n. And also used downcase to make input case insensitive.
EDIT
As mentioned by #Jordan in the comments, there is no reason to use string interpolation for the puts statement. So it's enough to call the method directly.
There are a bunch of issues with your code. Try something more like:
def reply(response)
return 'I do too' if response == 'yes'
return 'Dogs are better' if response == 'no'
'Invalid response!'
end
puts 'Do you like cats?'
response = gets().chomp()
puts reply(response)
Pay attention to the variable names. If you keep them descriptive, it is easier to spot mistakes.
Your script has no n local variable defined that you are passing to your ask(n) method at the end.
Rename your ask variable that your script gets from user to answer for example and pass it to your ask method at the end like so:
Updated code to fix other problem I did not see in the first run.
puts "Do you like cats?"
answer = gets.chomp
def ask(n)
(n == 'yes') ? "I do too" : "Dogs are better"
end
puts "#{ask(answer)}"

What's the major difference between if statements and unless statements?

I am new to programming and I don't understand what the difference is. I am coding in Ruby.
if should be obvious: execute a block of code if the condition is true.
unless is the opposite: execute a block of code if the condition is false.
http://www.codecademy.com/glossary/ruby/if-unless-elsif-and-else
unless is equal to if not
for example:
a= false
unless a
puts "hello"
end
=> hello
if not a
puts "hello"
end
=> hello
One of the goals of Ruby language is to become more closer to the real English. And keywords if and unless are really good examples of that. Look at this:
if animal.can_speak?
animal.say 'Woof!'
end
# moreover, it can be transformed in one-line, which looks pretty awesome:
animal.say 'Woof!' if animal.can_speak?
unless is an opposite of the if and instead of writing:
if not animal.can_speak?
puts "This animal can't speak"
end
we can use unless, that usually considered as more natural way:
unless animal.can_speak?
puts "This animal can't speak"
end
# it also has one-line approach:
puts "..." unless animal.can_speak?

is this a valid ruby syntax?

if step.include? "apples" or "banana" or "cheese"
say "yay"
end
Several issues with your code.
step.include? "apples" or "banana" or "cheese"
This expression evaluates to:
step.include?("apples") or ("banana") or ("cheese")
Because Ruby treats all values other than false and nil as true, this expression will always be true. (In this case, the value "banana" will short-circuit the expression and cause it to evaluate as true, even if the value of step does not contain any of these three.)
Your intent was:
step.include? "apples" or step.include? "banana" or step.include? "cheese"
However, this is inefficient. Also it uses or instead of ||, which has a different operator precedence, and usually shouldn't be used in if conditionals.
Normal or usage:
do_something or raise "Something went wrong."
A better way of writing this would have been:
step =~ /apples|banana|cheese/
This uses a regular expression, which you're going to use a lot in Ruby.
And finally, there is no say method in Ruby unless you define one. Normally you would print something by calling puts.
So the final code looks like:
if step =~ /apples|banana|cheese/
puts "yay"
end
The last two terms appear to Ruby as true, rather than having anything to do with the include? phrase.
Assuming that step is a string...
step = "some long string with cheese in the middle"
you could write something like this.
puts "yay" if step.match(/apples|banana|cheese/)
Here's a way to call step.include? on each of the arguments until one of them returns true:
if ["apples", "banana", "cheese"].any? {|x| step.include? x}
It's definitely not what you appear to be wanting. The include? method takes in a String, which is not what "apples" or "banana" or "cheese" produces. Try this instead:
puts "yay" if ["apples", "banana", "cheese"].include?(step)
But it's unclear from the context what step is supposed to be. If it's just the single word, then this is fine. If it can be a whole sentence, try joel.neely's answer.
The closest thing to that syntax that would do what you appear to want would be something like:
if ["apples", "banana", "cheese"].include?(step)
puts "yay"
end
But one of the other suggestions using a regex would be more concise and readable.
Assuming step is an Array or a Set or something else that supports set intersection with the & operator, I think the following code is the most idiomatic:
unless (step & ["apples","banana","cheese"]).empty?
puts 'yay'
end
I'll add some parentheses for you:
if (step.include? "apples") or ("banana") or ("cheese")
say "yay"
end
(That would be why it's always saying "yay" -- because the expression will always be true.)
Just to add another side to this...
If step is an Array (as calling include? seems to suggest) then maybe the code should be:
if (step - %w{apples banana cheese}) != step
puts 'yay'
end

Resources