Ruby yield 在 eval 下无效的问题

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

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 请教大家,如何解决这个问题,谢谢。

其实重点是 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 哭了 好牛逼。。

因为楼主的代码看起来也是在匿名 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
luikore Y combinator 的应用: 嵌套布局保序渲染... 提及了此话题。 02月24日 13:57
需要 登录 后方可回复, 如果你还没有账号请 注册新账号