Ruby捕获NoMethodError并从发生异常的地方继续执行

Ove*_*ked 6 ruby metaprogramming

在Ruby中,我想捕获在另一个对象中的对象上生成的NoMethodError,然后将一些值返回到引发异常的位置并继续执行.有现成的方法吗?

我想出的最好的是:

class Exception
  attr_accessor :continuation
end

class Outer
  def hello
    puts "hello"
  end

  class Inner
    def world
      puts "world"
    end
    def method_missing(method, *args, &block)
      x = callcc do |cc|
        e = RuntimeError.exception(method)
        e.continuation = cc
        raise e
      end
      return x
    end
  end

  def inner(&block)
    inner = Inner.new
    begin
      inner.instance_eval(&block)
    rescue => e
      cc = e.continuation
      cc.call(hello())
    end
    inner
  end
end

o = Outer.new
o.inner do
  hello
  world
end
Run Code Online (Sandbox Code Playgroud)

这打印

hello
world
Run Code Online (Sandbox Code Playgroud)

有没有更好的方法来使用Ruby现有的元编程库?基本上,我不确定callcc是否会继续存在.

谢谢.

Ali*_*kau 6

这个简单的方法怎么样:

class Outer
  def hello
    puts "hello"
  end

  class Inner
    def initialize outer
      @outer = outer
    end

    def world
      puts "world"
    end

    def method_missing(method, *args, &block)
      @outer.send(method, *args, &block)
    rescue NoMethodError # you can also add this
      puts "#{method} is undefined in both inner and outer classes"
    end
  end

  def inner(&block)
    inner = Inner.new self
    inner.instance_eval(&block)
    inner
  end
end

o = Outer.new
o.inner do
  hello
  cruel
  world
end
Run Code Online (Sandbox Code Playgroud)

会打印

hello
cruel is undefined in both inner and outer classes
world
Run Code Online (Sandbox Code Playgroud)

在这种情况下,如果内部类没有定义所需的方法,则使用Object#send将其委托给外部类.当类没有定义委托方法时,您可以捕获NoMethodError内部method_missing以控制情况Outer.


更新 您还可以使用光纤来解决问题:

class Outer
    def hello
        puts "hello"
    end

    class Inner
        def world
            puts "world"
        end

        def method_missing(method, *args, &block)
            Fiber.yield [method, args, block] # pass method args to outer
        end
    end

    def inner(&block)
        inner = Inner.new
        f = Fiber.new { inner.instance_eval(&block) }
        result = nil # result for first fiber call does not matter, it will be ignored
        while (undef_method = f.resume result) # pass method execution result to inner
            result = self.send(undef_method[0], *undef_method[1], &undef_method[2])
        end
        inner
    end
end
Run Code Online (Sandbox Code Playgroud)