Ruby перехватывает NoMethodError и продолжает выполнение с того места, где произошло исключение - PullRequest
6 голосов
/ 28 января 2012

В 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 продолжит существовать.

Спасибо.

Ответы [ 2 ]

6 голосов
/ 29 января 2012

А как насчет этого простого подхода:

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 . Вы можете поймать NoMethodError внутри method_missing, чтобы контролировать ситуацию, когда класс Outer не определяет делегированный метод.


UPDATE Вы также можете использовать волокна для решения проблемы:

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
3 голосов
/ 28 января 2012

В 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 должны выполнить

Надеюсь, это поможет

...