【问题标题】:Ruby catching NoMethodError and continue execution from where exception occurredRuby 捕获 NoMethodError 并从发生异常的地方继续执行
【发布时间】:2012-01-28 16:26:42
【问题描述】:

在 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

打印出来

hello
world

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

谢谢。

【问题讨论】:

    标签: ruby metaprogramming


    【解决方案1】:

    这个简单的方法怎么样:

    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
    

    将打印

    hello
    cruel is undefined in both inner and outer classes
    world
    

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


    更新 你也可以使用fiber来解决这个问题:

    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
    

    【讨论】:

    • Alex - 我喜欢您的第一个简单解决方案,尽管我不确定它是否优于异常延续方法。不是更糟,只是不同的权衡。例如。现在你的内部必须与外部绑定,所以在嵌套时你不能使用一些通用类而不是内部。但是您的解决方案确实避免使用 callcc。我正在寻找有人告诉我 callcc 方法是否会出现问题,因为 callcc 可能不会(或可能)继续存在。谢谢。
    • @Overclocked,正如 Matz(Ruby 的创造者)在他的 Ruby 编程语言 一书中指出的那样,Because they are no longer well supported, continuations should be considered a curiosity, and new Ruby code should not use them。你是对的,他们的使用是有问题的。如果您真的不想与外部类紧密耦合,那么您可以使用我第二个示例中的Fibers 将控制权转移到内部类。
    • Alex - 感谢您的来信。我一直在寻找类似的东西。太糟糕了;续集很棒。但是您的纤维示例是一个很好的替代品。谢谢。
    【解决方案2】:

    Ruby 有一个名为throw 的关键字,可用于向上传播错误。我无法从你的帖子中真正看出你想要在哪个区块中,但它是这样的:

    class Outer
      catch :NoMethodError do
      #process the exception thrown from inner
      end
    
      class Inner
        def some_method
          ##...some other processing
          throw :NoMethodError if #....
          #remaining statements
        end
      end
    end
    

    在 throw 语句和 catch 块之后执行 some_method 中的剩余语句应该执行

    希望对你有帮助

    【讨论】:

    • 我正在寻找某种机制,我可以将值返回到“抛出”发生的位置,然后继续执行。有没有办法在像 throw 这样的内置东西中做到这一点?
    • 我不确定你将如何返回一个值,这可能是可能的,我只是不知道有什么方法。您可以为内部类创建另一个实例变量并从外部类更新它;这可能是一种方法。
    • @Overclocked “哪里”会是什么样子?
    • @Phrogz - 在我的问题中,在底部,我打了招呼和世界。在 Inner 中评估时,对 hello 的调用将不起作用,因为 Inner#hello 不存在。但我想在那里评估 Outer#hello 。我不希望Inner继承Outer,所以我需要从Inner中捕获丢失的方法异常,评估Outer#hello,将结果返回到block,并继续执行Inner#world。
    • 只想指出,在示例中,我认为您混淆了 throw/cache 在 ruby​​ 中的工作方式。 catch 需要一个“标签”和一个要执行的块,如果在执行块期间throw 有什么东西,它将向上搜索堆栈到带有相应标签的第一个 catch 并在 catch 语句之后继续执行。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-07
    • 1970-01-01
    • 2012-09-15
    • 2012-02-08
    • 1970-01-01
    相关资源
    最近更新 更多