Use break keyword in the method body - ruby

I have two loops here:
loop do
prompt(messages('APR_amt', LANGUAGE))
APR_amt = Kernel.gets.chomp
if valid_number?(APR_amt)
break
else
prompt(messages('not_valid_num', LANGUAGE))
end
end
loan_duration = ''
loop do
prompt(messages('loan_duration', LANGUAGE))
loan_duration = Kernel.gets().chomp()
if valid_number?(loan_duration)
break
else
prompt(messages('not_valid_num', LANGUAGE))
end
end
This part keeps on repeating for every loop:
if valid_number?(loan_duration)
break
else
prompt(messages('not_valid_num', LANGUAGE))
end
Just different variable passing by on it.
Now what I did is that I created a method for it to shortened my codes:
def check_number(varname)
if valid_number?(varname)
break
else
prompt(messages('not_valid_num', LANGUAGE))
end
end
But this one did not work. Instead I got an error that pertains to break.
How can I create a method that will work on all of my variables?

You can raise StopIteration instead of calling break. But note that they are not equivalent:
raise StopIteration jumps of a loop created with the loop statement. The exception is not handled by loops created with statements such as while, for, or methods such as each. It has a dynamic scope, which means that it goes up the call stack until it finds the loop (which can be defined in a completely different place in the code).
break jumps out of any block. It has a lexical scope, which means that the block must enclose the break statement in the code. In your code there is no block around break (a method is not a block), and that's the reason you got an error.

Related

Loop resets variable

I am encountering the following unexpected behavior while running the following loop:
outside_var = 'myString'
loop do
inside_var ||= outside_var
result = SomeCalculation.do_something(inside_var)
inside_var = result[:new_inside_var_value]
end
Now, on the first iteration inside_var gets set to outside_var, which is the expected behavior. Just before the next iteration I set inside_var to something else (depending on the result I got from the calculation inside the loop). This assignment works (printing inside_var at the very bottom of the loop confirms that). On the next iteration, however, inside var goes back to the original state, which is something I didn't anticipate. Why is it doing that and how can I set this variable inside this loop?
I am running Ruby 2.6.5 with Rails 6.
This is a scoping issue. inside_var is scoped to the block. One might check the binding, it changes.
outside_var = 'myString'
2.times do
puts "INSIDE 1: #{defined?(inside_var).nil?} → #{binding}"
inside_var ||= outside_var
puts "INSIDE 2: #{inside_var}"
end
#⇒ INSIDE 1: true → #<Binding:0x000055a3936ee0b0>
# INSIDE 2: myString
# INSIDE 1: true → #<Binding:0x000055a3936edc50>
# INSIDE 2: myString
That said, every time the execution enters the block, the binding is reset, that’s why one should not expect the variables from another scope (with another binding) to exist.
When you do a new iteration inside the loop, you are going to reset everything. I suggest you to modify the var outside the loop to preserve the value inside. Something like this:
result_var = 'myString' # value on the first iteration
loop do
result = SomeCalculation.do_something(result_var)
result_var = result[:new_inside_var_value] # at the end of the first iteration you are already overriding this value
end

Is there a built-in way to check if #next or #peek will raise StopIteration?

I'm working with a few iterators where I have to do something along these lines (enum is an enumerator)
enums_with_zero << enum.rewind if enum.peek == 0
which normally works fine, but this is after there's already been #next called on the enum a few times. The issue with this is that the enum could be at the end and with a few values passed for enum, I have hit the issue where enum.peek raises StopIteration because the enum is completed. Is there a way I could put in a guard to check if enum.peek or enum.next will cause StopIteration before I call it. Something that would have the behavior of this for example?
class Enumerator
def has_next?
begin
peek && true
rescue StopIteration
false
end
end
end
You can rescue the StopIteration explicitly, but there's also the idea that the loop method internally rescues a StopIteration exception by simply exiting the loop. (Inside loop, raise StopIteration has the same effect as break.)
This code simply exits the loop when you try to peek past the end:
a = %w(a b c d e).to_enum
loop do
print a.peek
a.next
end
The code outputs abcde. (It also transparently raises and rescues StopIteration.)
So, if you want to simply ignore the StopIteration exception when you try to peek past the end, just use loop.
Of course, once you peek past the end, you'll get dumped out of the loop. If you don't want that, you can use while and rescue to customize behavior. For example, if you want to avoid exiting if you peek past the end, and exit when you iterate past the end using next, you could do something like this:
a = %w(a b c d e).to_enum
while true
begin
print a.peek
rescue StopIteration
print "\nTried to peek past the end of the enum.\nWe're gonna overlook that.\n"
end
x = a.next rescue $!
break if x.class == StopIteration
end
p 'All done!'
The last two lines in the loop do the same thing as this, which you could use instead:
begin
a.next
rescue StopIteration
break
end
A point to make is that handling StopIteration is Ruby's intended way of dealing with getting to the end of an iterator. Quoting from Matz's book The Ruby Programming Language:
External iterators are quite simple to use: just call next each time you want another
element. When there are no more elements left, next will raise a StopIteration exception.
This may seem unusual—an exception is raised for an expected termination
condition rather than an unexpected and exceptional event. (StopIteration is a descendant
of StandardError and IndexError; note that it is one of the only exception
classes that does not have the word “error” in its name.) Ruby follows Python in this
external iteration technique. By treating loop termination as an exception, it makes
your looping logic extremely simple; there is no need to check the return value of
next for a special end-of-iteration value, and there is no need to call some kind of
next? predicate before calling next.

Rubocop rule: Never use 'do' with multi-line 'while

I have the following code
# colours a random cell with a correct colour
def colour_random!
while true do
col, row = rand(columns), rand(rows)
cell = self[row,col]
if cell.empty? then
cell.should_be_filled? ? cell.colour!(1) : cell.colour!(0)
break
end
end
end
it's not that important what's doing, although it should pretty obvious. The point is that Rubocop gives me a warning
Never use 'do' with multi-line 'while
Why should I not do that? How should I do it then?
while is a keyword,so you don't need to pass a block. Without do..end it will work fine. The below is fine
def colour_random!
while true
col, row = rand(columns), rand(rows)
cell = self[row,col]
if cell.empty? then
cell.should_be_filled? ? cell.colour!(1) : cell.colour!(0)
break
end
end
end
while is a keyword, and if you pass a block to it, like do..end, it still works as you asked it to do, by not throwing any error, rather just a warning. But it could be dangerous if you try to pass a Proc or Method object to it, and dynamically try to convert it to a block using & keyword, as we do generally. That means
# below code will work as expected just throwing an warning.
x = 2
while x < 2 do
#code
end
But if you try to do by mistake like below
while &block # booom!! error
The reason is while is a keyword, which don't support any to_proc method to satisfy your need. So it can be dangerous.
Ruby style guide also suggested that Never use while/until condition do for multi-line while/until
I think the reason is as Nobuyoshi Nakada said in the mailing list
loop is a kernel method which takes a block. A block introduces new local variable scope.
loop do
a = 1
break
end
p a #=> causes NameError
while doesn't.
while 1
a = 1
break
end
p a #=> 1
Ruby actually has a shortcut for while true: the loop statement.
def colour_random!
loop do
col, row = rand(columns), rand(rows)
cell = self[row,col]
if cell.empty? then
cell.should_be_filled? ? cell.colour!(1) : cell.colour!(0)
break
end
end
end

Break out a loop from within a (yielded) block inside the loop

jobs.each do |job|
msg job.name do
break if stop_all_jobs?
job.run!
end
end
def msg(msg, &block)
puts 'START ' + msg
yield
puts 'END ' + msg
end
In the above example break does not break out of the loop as expected. It only breaks out of the msg code block.
This seems a little odd, but I guess it is based on context, that said, how do I break out of the loop from code which is within a yielded code block?
One way is to use throw/catch. No, not exceptions, Ruby has a separate control-of-flow feature that works a bit like exceptions, without all the overhead (although I must admit I'm not sure that there isn't any overhead in using it):
catch :stop_all_jobs do
msg job.name do
throw :stop_all_jobs if stop_all_jobs?
job.run!
end
end
You can even pass a value as the second argument to throw which will be the result of the catch block.
A potentially more readable solution would, of course, be to pack the code up in a method and use return in place of break. But that wouldn't be as fun.
Use next instead of break.

Does begin . . . end while denote a 'block'?

temp = 98.3
begin
print "Your temperature is " + temp.to_s + " Fahrenheit. "
puts "I think you're okay."
temp += 0.1
end while temp < 98.6
In the above example, is everything between begin and end a block?
I'm still confused what a block is.
If you can't call it a block, what would you call that chunk of code between begin and end? Is it ok to call it a chunk?
Block has a special meaning in Ruby. According to Matz, Ruby's creator, you can look at a block as a nameless function - typically something that can be yielded into, and which may also take parameters.
You may see the following kind of disamiguation when describing Ruby syntax:
begin...end (what is called block in other languages) may sometimes be referred to simply as what it is, i.e. an expression (which may in turn contain other expressions - an expression is simply something that has a return value) in Ruby. Some references will still call it a begin/end block, or a code block, adding somewhat to the confusion
do...end or {...} will always be referred to as a block in Ruby
For examples, peruse the the Ruby syntax man page, e.g.
begin expression end
expression while expression
loop block
For further reading, see:
Programming Ruby
Ruby (from other languages)
Much, much more documentation
begin/end are strictly control flow, not blocks.
begin
puts "hi"
end
# => "hi"
The code runs immediately. If it was a block, it would have to been called somehow in order for the code in it to run, as in this example:
def a_method; end
a_method { puts "hi" }
# nothing..
def a_method
yield
end
a_method { puts "Hi!" }
# => "Hi!"

Resources