Call Next on Ruby Loop from External Method

call next on ruby loop from external method

You complex method could return a boolean, and then you compare on your loop like this:

def my_complex_method(item)
true if item.even?
end

(1..10).each do |a|
next if my_complex_method(a)
puts a
end

A simple approach, but different from the try catch one.

UPDATE

As item.even? already return a boolean value, you don't need the true if item.even? part, you can do as follow:

def my_complex_method(item)
item.even?
end

Call next for outer loop from within a method

Yes. You should use throw and catch.

bot.rb

while Option.daemon_active?(daemon: "bot")
catch(:foo) do
...
Trade.market_order
...
end
end

trade.rb

class Trade
def self.market_order
...
response = exchange.market_sell
throw :foo if response["state"] == false
end
end

Can you pass a next back to the function that called the current function?

next is only valid in the direct context of a loop. Once you call into a method, you are no longer directly in that loop context. You cannot use next to short-circuit the outer loop like this.

You have a couple of options:

  1. Return statuses from your predicate functions (which is what you should do, from a predicate!) and short-circuit the loop based on those, or
  2. Use Ruby's catch...throw construct (which is NOT its raise/rescue exception handler, but is instead something like a block-scoped GOTO statement)

Option 1: Returning statuses. This is the most appropriate method, IMO. Predicate methods (those ending in ?) should conventionally return a boolean and be idempotent (that is, should have no side effects, such as logging a statement). They are conventionally used to ask a yes/no question. Deciding what to do based on that question should ideally be outside of their scope.

def card_handler
cards.each do |card|
#some non-relevant code is here on my end
if already_sent?
puts '\n Order info: \n id: #{id} \n Email already sent'
next
end
end
end

def already_sent?
case list_action
when 145
a_s_helper(p3_label)
when 145
a_s_helper(p2_label)
when 147
a_s_helper(p1_label)
end
end

def a_s_helper(label)
card::card_labels.include? label
end

This causes your helpers to return a true or false value to your loop, which can decide to log a message and go to the next iteration.

Option 2: catch...throw

def card_handler
cards.each do |card|
# Put all your code that should nomally run inside the catch block. If
# the message :email_sent is thrown, then Ruby will zip up the stack and
# resume execution at the end of the block. This will skip any unexecuted
# code in the block, essentially terminating the execution.
catch :email_sent do
already_sent?
end
end
end

def already_sent?
# ...
end

def a_s_helper(label)
# ...
throw :email_sent if card::card_labels.include? label
# ...
end

You may be tempted to use option 2, since it requires less careful control over method construction, but it is perilously close to exceptions as flow control which are widely considered an antipattern (it's essentially a slightly more fancy GOTO, which is notorious for making code difficult to read and debug). If you can simply return a status from your helpers and decide whether or not to continue the loop based on that, you should do so.

How to break outer cycle in Ruby?

What you want is non-local control-flow, which Ruby has several options for doing:

  • Continuations,
  • Exceptions, and
  • throw/catch

Continuations

Pros:

  • Continuations are the standard mechanism for non-local control-flow. In fact, you can build any non-local control-flow (subroutines, procedures, functions, methods, coroutines, state machines, generators, conditions, exceptions) on top of them: they are pretty much the nicer twin of GOTO.

Cons:

  • Continuations are not a mandatory part of the Ruby Language Specification, which means that some implementations (XRuby, JRuby, Ruby.NET, IronRuby) don't implement them. So, you can't rely on them.

Exceptions

Pros:

  • There is a paper that proves mathematically that Exceptions can be more powerful than Continuations. IOW: they can do everything that continuations can do, and more, so you can use them as a replacement for continuations.
  • Exceptions are universally available.

Cons:

  • They are called "exceptions" which makes people think that they are "only for exceptional circumstances". This means three things: somebody reading your code might not understand it, the implementation might not be optimized for it (and, yes, exceptions are godawful slow in almost any Ruby implementation) and worst of all, you will get sick of all those people constantly, mindlessly babbling "exceptions are only for exceptional circumstances", as soon as they glance at your code. (Of course, they won't even try to understand what you are doing.)

throw/catch

This is (roughly) what it would look like:

catch :aaa do
stuff.each do |otherstuff|
foo.each do |bar|
throw :aaa if somethingbad
end
end
end

Pros:

  • The same as exceptions.
  • In Ruby 1.9, using exceptions for control-flow is actually part of the language specification! Loops, enumerators, iterators and such all use a StopIteration exception for termination.

Cons:

  • The Ruby community hates them even more than using exceptions for control-flow.

Skip a Ruby iterator from an external method

Is it an option to move the next into the map block? Something like this:

def external_method(n)
n.valid? && n
end

(1..9).map { |i| external_method(i) || next }

Btw I am not sure if the code in your question is just an example or real code. But it feels to me like the next doesn't make any sense, because it is called last in the block and moving to the next element would have been the next step in the map anyway.

Can someone explain why this Ruby next if works in an until loop?

First, until is a top-testing loop. The entire block must loop before the until condition is tested again; it is not tested after every statement within the loop. You have to reach end or next before the until will evaluate again.

Second, <statement> if <condition> is a ruby shorthand for if <condition> <statement> end. It allows you to write a simple conditional on one line without sacrificing readability. The next will only execute at the last iteration of the arr.each_index loop, going up the stack to the until condition, by which time sorted will have been set to false.

To see how this works, try running the following modification:

#!/usr/bin/ruby

def sorting(arr)
puts "starting with #{arr}"
sorted = false
until sorted
sorted = true
arr.each_index do |i|
if i == arr.length - 1
puts "'next' when i == #{i}, arr = #{arr}"
next
end
if arr[i] > arr[i + 1]
puts "swapping at #{i}: #{arr[i]} <=> #{arr[i+1]}"
arr[i], arr[i+1] = arr[i+1], arr[i]
sorted = false
end
end
end
arr
end

p sorting([7,4,5,1,2,3])

The output of this program is:

starting with [7, 4, 5, 1, 2, 3]
swapping at 0: 7 <=> 4
swapping at 1: 7 <=> 5
swapping at 2: 7 <=> 1
swapping at 3: 7 <=> 2
swapping at 4: 7 <=> 3
'next' when i == 5, arr = [4, 5, 1, 2, 3, 7]
swapping at 1: 5 <=> 1
swapping at 2: 5 <=> 2
swapping at 3: 5 <=> 3
'next' when i == 5, arr = [4, 1, 2, 3, 5, 7]
swapping at 0: 4 <=> 1
swapping at 1: 4 <=> 2
swapping at 2: 4 <=> 3
'next' when i == 5, arr = [1, 2, 3, 4, 5, 7]
'next' when i == 5, arr = [1, 2, 3, 4, 5, 7]
[1, 2, 3, 4, 5, 7]

This is of course academic: the proper way to sort the array would be with Array#sort.

Note: the next is only necessary at all because on the last iteration of the each_index loop, i + 1 will be out of range for the array, which would cause the next line which accesses arr[i + 1] to fail (it will evaluate to nil, and you can't compare an Integer to nil). Alternate ways of doing this would be to modify the conditional around the swap testing the index, or change the outside of the loop enumerating the array to be a smaller range.

Modifying the conditional, eliminating the next, which works because logical-and conditionals are evaluated left to right and the interpreter stops as soon as the first one is false:

def sorting(arr)
sorted = false
until sorted
sorted = true
arr.each_index do |i|
if (i < arr.size - 1) && (arr[i] > arr[i + 1])
arr[i], arr[i+1] = arr[i+1], arr[i]
sorted = false
end
end
end
arr
end

p sorting([7,4,5,1,2,3])

Changing the range of the loop, which is better in that the loop executes fewer times:

def sorting(arr)
sorted = false
until sorted
sorted = true
(0..arr.size - 2).each do |i|
if (arr[i] > arr[i + 1])
arr[i], arr[i+1] = arr[i+1], arr[i]
sorted = false
end
end
end
arr
end

p sorting([7,4,5,1,2,3])

next is akin to goto in other languages and should be avoided if possible.

Execute next from method

def skip_if_three(count)
return unless count == 3
puts "three detected, let's skip this loop!"
throw(:three)
end

5.times do |count|
catch(:three) do
skip_if_three(count)
puts count
end
end

result:

0
1
2
three detected, let's skip this loop!
4

def three?(count)
return unless count == 3
puts "three detected, let's skip this loop!"
true
end

5.times do |count|
puts count unless three?(count)
end

result:

0
1
2
three detected, let's skip this loop!
4

def three?(count)
return unless count == 3
puts "three detected, let's skip this loop!"
true
end

5.times do |count|
next if three?(count)
puts count
end

result:

0
1
2
three detected, let's skip this loop!
4

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

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.



Related Topics



Leave a reply



Submit