动态嵌套红宝石块

时间:2009-07-29 09:07:45

标签: ruby

想象一下,我有一些带有run方法的资源对象,它在为该资源持有的锁下执行block参数。例如,像这样:

r = Resource("/tmp/foo")
r.run { ... }

如何编写一个ruby方法,该方法获取一系列资源并在为所有资源保存的锁下执行其block参数,例如:

def using_resources(*res, &block)
  r[0].run do; r[1].run do; r[2].run do ...   

    yield;

  end; end; end; ...
end

有可能吗?

3 个答案:

答案 0 :(得分:8)

在我看来,最好使用递归

以下是代码:

def using_resources(*res, &block)

   first_resource = res.shift

   if res.length > 0 
     first_resource.run do
      using_resources(*res, &block)
     end
   else
     first_resource.run do
       block.call
     end
   end

end

并像这样使用它:

using_resources Resource.new('/tmp'), Resource.new('http://stackoverflow.com') do
  do_some_processing
end

你说,“这需要一系列资源。”如果您已经Array并且需要使用它,则可以在呼叫之外展开Array

 using_resources *my_array do
   do_some_processing
 end

或者在方法定义中,您可以使用ArrayResource列表来调用它:

def using_resources(*res, &block)
  res = [*res]
  # rest as above
end

答案 1 :(得分:7)

您也可以使用#inject

执行此操作
def using_resources(*resources, &block)
  (resources.inject(block){ |inner,resource| proc { resource.run(&inner) } })[]
end

当您单步执行数组时,将每个资源的前一个Proc的调用包装在新的Proc中,然后将其传递给下一个资源。这会以相反的顺序获取锁定(给定的最后一个资源是第一个解锁的资源),但可以使用resources.reverse.inject ...

更改

答案 2 :(得分:0)

对于run()产生您想在最里面的块中使用的值的情况,我想在@rampion的解决方案中发布扩展名,如下所示:

def using_resources(*res, &block)
  r[0].run do |v0|; r[1].run do |v1|; r[2].run do |v2| ...

    yield [v0, v1, v2];

  end; end; end; ...
end

例如,如果runFile.open类似,则会出现这种情况,它会产生可在给定块中使用的资源(例如,文件对象),但在使用后将其拆除您的封锁完成了。

功能如下:

def nested_do( args, func, &block )
  args.reverse.inject(block) do |inner, a|
      Proc.new do |acc|
          func.call(a) do |v|
              acc.append(v)
              inner.call(acc)
          end
      end
  end
  .call([])
end

要嵌套对run的调用并收集其产生的值,您可以这样做:

res = [ Resource('a'), Resource('b'), Resource('c') ]
func = Proc.new {|r,&b| r.run(&b)}
nested_do( res, func ) do |vals|
    puts("Computing with yielded vals: #{vals}")
end

示例

这里的功能类似于File.open,但它会打开“秘密”资源, 呼叫者永远无法在呼叫者的范围内使用的原因,因为 在yield()返回后将其拆除:

def open_secret(k)
    v = "/tmp/secret/#{k}"
    puts("Setup for key #{k}")
    yield v
    puts("Teardown for key #{k}")
end

以下是如何动态嵌套对open_secret的调用:

nested_do([:k1,:k2,:k3], Proc.new {|k,&b| open_secret(k,&b)} ) do |rs|
    puts("Computing with 'secret' resources SIMULTANEOUSLY: #{rs}")
end

打印:

Setup for key k1
Setup for key k2
Setup for key k3
Computing with 'secret' resources SIMULTANEOUSLY: ["/tmp/secret/k1", "/tmp/secret/k2", "/tmp/secret/k3"]
Teardown for key k3
Teardown for key k2
Teardown for key k1

更新

事后看来,使用inject需要大量的脑力劳动。这是使用递归的高级实现:

def nested_do(args, func, acc=[], &block)
    return block.call(acc) if args.size == 0

    a = args.pop
    func.call(a) do |v|
        acc.append(v)
        nested_do( args, func, acc, &block)
    end
end