Accessing a passed block in Ruby

梦想的初衷 提交于 2020-05-28 06:04:35

问题


I have a method that accepts a block, lets call it outer. It in turn calls a method that accepts another block, call it inner.

What I would like to have happen is for outer to call inner, passing it a new block which calls the first block.

Here's a concrete example:

class Array
  def delete_if_index
    self.each_with_index { |element, i| ** A function that removes the element from the array if the block passed to delete_if_index is true }
  end
end

['a','b','c','d'].delete_if_index { |i| i.even? }
  => ['b','d']

the block passed to delete_if_index is called by the block passed to each_with_index.

Is this possible in Ruby, and, more broadly, how much access do we have to the block within the function that receives it?


回答1:


You can wrap a block in another block:

def outer(&block)
  if some_condition_is_true
    wrapper = lambda {
      p 'Do something crazy in this wrapper'
      block.call # original block
    }
    inner(&wrapper)
  else
    inner(&passed_block)
  end
end

def inner(&block)
  p 'inner called'
  yield
end

outer do
  p 'inside block'
  sleep 1
end

I'd say opening up an existing block and changing its contents is Doing it WrongTM, maybe continuation-passing would help here? I'd also be wary of passing around blocks with side-effects; I try and keep lambdas deterministic and have actions like deleting stuff in the method body. In a complex application this will likely make debugging a lot easier.




回答2:


Maybe the example is poorly chosen, but your concrete example is the same as:

[1,2,3,4].reject &:even?

Opening up and modifying a block strikes me as code smell. It'd be difficult to write it in a way that makes the side effects obvious.

Given your example, I think a combination of higher order functions will do what you're looking to solve.

Update: It's not the same, as pointed out in the comments. [1,2,3,4].reject(&:even?) looks at the contents, not the index (and returns [1,3], not [2,4] as it would in the question). The one below is equivalent to the original example, but isn't vary pretty.

[1,2,3,4].each_with_index.reject {|element, index| index.even? }.map(&:first)



回答3:


So here's a solution to my own question. The passed in block is implicitly converted into a proc which can be received with the & parameter syntax. The proc then exists inside the closure of any nested block, as it is assigned to a local variable in scope, and can be called by it:

class Array
  def delete_if_index(&proc)
    ary = []
    self.each_with_index { |a, i| ary << self[i] unless proc.call(i) }
    ary
  end
end

[0,1,2,3,4,5,6,7,8,9,10].delete_if_index {|index| index.even?}

  => [1, 3, 5, 7, 9]

Here the block is converted into a proc, and assigned to the variable proc, which is then available within the block passed to each_with_index.



来源:https://stackoverflow.com/questions/7470446/accessing-a-passed-block-in-ruby

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!