Ruby yield 在 eval 下无效的问题

ibachue · 2013年05月13日 · 最后由 seiran 回复于 2013年05月17日 · 3150 次阅读

Hi all, 希望实现这样一个功能,就像Rack Middleware一样,通过注册的方法层层深入知道endpoint为止。但是和Rack Middleware的区别是

  1. 不用定义一个类,只要用一个方法传入一个block声明下就可以了
  2. 使用yield而不是Rack的@app.call这样并不优雅的方法

我现在写了这些代码:

def filter(name, &blk)
  @__filters ||= []
  @__filters << [name, blk]
end

def run_filters(&blk)
  m = Module.new do
    extend self
    @__filter_endpoint = blk
  end
  code = '@__filter_endpoint.call'
  @__filters.each do |name, block|
    m.send(:define_method, :"__filter_#{name}", &block)
  end
  @__filters.each do |name, block|
    code = "send(:\"__filter_#{name}\") { #{code} } "
  end
  m.module_eval code.tap {|c| puts c}
end

filter '1' do
  puts 1
  yield
  puts 1
end

filter '2' do
  puts 2
  yield
  puts 2
end

filter '3' do
  puts 3
  yield
  puts 3
end

run_filters do
  puts 'core'
end

可以看到输出信息中已经有send(:"__filter_3") { send(:"__filter_2") { send(:"__filter_1") { @__filter_endpoint.call } } },可见去eval的代码是正确的。 并且还输出了一个3,表示已经确实执行了__filter_3方法,但是跑到yield的时候出现了错误:no block given 请教大家,如何解决这个问题,谢谢。

共收到 9 条回复

其实重点是 long jump error...

简化了就是这样:

define_method(:f){ yield }
f{} #=> yield 跳到外面啦

貌似还不是很清楚? 其实相当于:

def yield2
  yield
end
define_method(:f){ yield2 } #=> 看到少个 block 了吧

解决方案:

define_method(:f){|&p| p.call }

#1楼 @luikore 楼主不想用 .call ...

#2楼 @blacktulipyield 就是看写的地方往上找 def / lambda 的...

@blacktulip 话说用 Fiber.yield 应该是能做的...

#4楼 @luikore 这思路... 好牛逼 Orz

#5楼 @blacktulip Fiber 就是为了可以这么搞而设计的吧...

@filters = []
def filter name, &p
  @filters << p
end

def run name, &p
  filters = @filters.map{|f| Fiber.new(&f) }
  filters.each &:resume
  yield
  filters.reverse_each &:resume
end

filter '1' do
  puts '1('
  Fiber.yield
  puts ')1'
end

filter '2' do
  puts '2('
  Fiber.yield
  puts ')2'
end

run '3' do
  puts '3()3'
end

#6楼 @luikore 哭了 好牛逼。。

#6楼 @luikore 给你跪了。

因为楼主的代码看起来也是在匿名Module m的上下文运行yield等价的东西……,但yield是和写块的位置绑定的,所以干脆手动制作一个上下文。。。除了闭包上下文还有self上下文呢,啊哈:(楼上Fiber的自动切换上下文很厉害的~)

def filter(name, &blk)
  @__filters ||= []
  @__filters << [name, blk]
end

def run_filters(&blk)
  m = Module.new do
    extend self
    @__filter_endpoint = blk

    def scope
      Thread.current[:__myblock] ||= []
    end
    def go
       scope[-1].call
    end
    private :scope, :go
  end
  code = '@__filter_endpoint.call'
  @__filters.each do |name, block|
    m.send(:define_method, :"__filter_#{name}"){|&b|
       scope.push b
       ret = instance_eval &block  
       scope.pop
       ret
    }
  end
  @__filters.each do |name, block|
    code = "send(:\"__filter_#{name}\") { #{code} } "
  end
  m.module_eval code.tap {|c| puts c}
end

filter '1' do
  puts 1
  go
  puts 1
end

filter '2' do
  puts 2
  go
  puts 2
end

filter '3' do
  puts 3
  go
  puts 3
end

run_filters do
  puts 'core'
end
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册