Skip a Ruby iterator from an external method

Viewed 83

Let's say you have a Ruby iterator that uses an external method in its block:

def external_method(n)
 n.valid? ? n : next  #this "next" expression will result in an error 
end

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

The question really is how can you breakor next from an external method used inside an iterator.

4 Answers

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.

The question really is how can you break or next from an external method used inside an iterator.

def external_method(n)
  n.valid? ? n : next
end

Even if it were possible – I'd have a hard time understanding what that method is doing. You shouldn't attempt to explicitly control the iteration from outside the iteration.

What about yielding valid values from your external method back to the loop? (doesn't have to be n, you could also yield a modified value)

def external_method(n)
  yield n if n.valid?
end

(1..9).map do |i|
  external_method(i) do |n|
    # do something with n
  end
end

This way, the block will only be executed if n.valid? and skipped otherwise. You might have to adjust the return value for external_method to play nicely with map. It currently returns just the block's result or nil.

Ruby 2.7 has Enumerable#filter_map

(1..10).filter_map { |i| i * 2 if i.even? } #=> [4, 8, 12, 16, 20]

(example from the linked doc)

If external_method needs to do a lot of stuff, but only if a condition on n is met, you can always return unless that condition is met, which has the effect of breaking or skipping to the next iteration, since the method call is the only operation in the block:

def external_method(n)
  return unless n.even?

  puts "running block on #{n}"
end

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

If the block has more than just the method call, and based on a condition within the method call, you want to skip all the rest of the block, you could try ruby's throw/catch:

def external_method(n)
  throw :next unless n.even?
end

(1..9).map do |i|
  catch(:next) do
    external_method(i)

    puts "running block on #{i}"
  end
end

Both of these code snippets output:

running block on 2
running block on 4
running block on 6
running block on 8
Related