nurettin
nurettin

Reputation: 11736

Injecting ruby code into block inside block

I use this pattern repeatedly:

pb= ProgressBar.new("blah_map", wtf.count)
newresult= cache("blah_map") do 
  result.map{ |r| pb.inc; { blah: r[:x] } }
end
pb.finish

or

pb= ProgressBar.new("blah_group", wtf.count)
newresult= cache("blah_group") do 
  result.group_by{ |r| pb.inc; "#{r[:x]}&#{r[:y]}" }
end
pb.finish

So naturally I would like to be able to do

def progress_cache(name, count, &block)
  pb= ProgressBar.new(name, count)
  inject_pb_inc(block) # how??
  # do some caching with yield if cache doesn't exist, don't mind this
  pb.finish
end

And use it as such:

newresult= progress_cache("lol", result.count) do 
  result.map do |r| 
    # progress_cache magically inserted a pb.inc here for pretty animation!  
    r[:omg] 
  end 
end

The question is, how to inject the pb.inc call into the block (map, group_by etc) inside progress_cache block?

Edit: rephrased the question

Upvotes: 2

Views: 2359

Answers (1)

Ben Taitelbaum
Ben Taitelbaum

Reputation: 7403

There are a few ways to accomplish this, with various tradeoffs in terms of expressiveness:

  1. Send the progress bar as a block param

    def progress_cache(name, count &block)
      pb = ProgressBar.new(name, count)
      result = block.call(pb)
      pb.finish
      result
    end
    

    and use it like

    newresult = progress_cache("lol", result.count) do |pb|
      result.map{|r| pb.inc; r[:omg]}
    end
    
  2. Create a new map function that automatically increments the progress bar (you could overwrite result.map directly, or provide result.map_with_progress, but I'll leave that up to you)

    def map_with_progress(container, name, &block)
      pb = ProgressBar.new(name, container.count)
      result = container.map{|obj| block.call(obj)}
      pb.finish
      result
    end
    

    and then use it like

    newresult = map_with_progress(result, "lol") { |r| r[:omg] }
    

    of course, since you're doing both a map and a group_by, you'd have to have two helper methods here, which might start getting messy.

  3. Use a higher-order function

    def function_with_progress(obj, func_name, name, count, &block)
      pb = ProgressBar.new(name, count)
      result = obj.__send__(func_name) do |param|
        pb.inc
        block.call(param)
      end
      pb.finish
      result
    end
    

    and then use it like

    newresult = function_with_progress(result, "map", "lol", result.count) do |r|
      r[:omg]
    end
    

    but I wouldn't recommend this approach as it's too abstract. It would work in a functional language like javascript or clojure, but I don't think it's appropriate for ruby.

Upvotes: 2

Related Questions