ruby

Saving the calculation inside a block to the outside


Assume I have in Ruby an Enumerable coll, and the following code (which of course does not produce the desired result):

# WRONG approach
def f(coll)
  pivot = coll.find do
    |element|
    t = element.calculate
    t > 0
  end
  pivot && t # Return the calculated value 
end

This does not work, since each block has its own scope for locals, and the t of the inner block is different from the t of the outer block. What is the most elegant way to fix it? I can think of the following possibilities:

    def f(coll)
      pivot = coll.find do
        |element|
        element.calculate > 0
      end
      pivot && pivot.calculate 
    end

Drawback: The last calculation is unnecessarily done twice.

    def f(coll)
      pivot = coll.find do
        |element|
        @t = element.calculate
        @t > 0
      end
      pivot && @t 
    end

Drawback: It's ugly design.

    def f(coll)
      coll.map(&:calculate).find {|x| x>0}
    end

Drawback: Unnecessary calculations

Is there any better way to do it, perhaps using binding to make the local variable accessible inside the block? If so, how can it be done?


Solution

  • The simplest way to have available a value outside a block is to use a local variable declared (by initialising) outside the block:

    t = nil
    
    call.find do |element|
      t = element.calculate
      t > 0
    end
    
    # here t is available
    

    There is a more complex but may be more idiomatic/functional way suited for your case - to calculate t lazily and using lazy #zip find a pair of element and t:

    ts = col.lazy.map { |e| e.calculate }
    element, t = enum.lazy.zip(ts).find { |e, t| t < 0 }