Подтвердить что ты не робот

Измените привязку Proc в Ruby

У меня есть этот код:

 l = lambda { a }
 def some_function
     a = 1
 end

Я просто хочу получить доступ к a лямбдой и специальной областью, которая в этом примере обозначила a уже где-то как внутри some_function, или совсем скоро в той же области, что и:

 l = lambda { a }
 a = 1
 l.call

Затем я обнаружил, что при вызове l он по-прежнему использует свою собственную привязку, но не новую, где он был вызван.

И затем я попытался использовать его как:

 l.instance_eval do
     a = 1
     call
 end

Но это тоже не удалось, странно, что я не могу объяснить, почему.

Я знаю, что одно из решений использует eval, в котором я мог бы использовать специальную привязку и выполнять некоторый код в тексте, но я действительно не хочу использовать его так.

И я знаю, что он может использовать глобальную переменную или переменную экземпляра. Однако на самом деле мой код находится в более глубокой встроенной среде, поэтому я не хочу разорвать завершенные части, если это не совсем необходимо.

Я передал класс Proc в документации, и я нашел имена функций binding, которые относятся к контексту Proc. Хотя функция предоставила только доступ к ее привязке, но не может ее изменить, кроме использования Binding#eval. Он также оценивает текст, что я и не люблю делать.

Теперь вопрос в том, есть ли у меня лучший (или более элегантный) способ реализовать это? Или использование eval уже является обычным способом?

Изменить, чтобы ответить на @Andrew:
Хорошо, это проблема, с которой я столкнулся, когда пишу лексический синтаксический анализатор, в котором я определил массив с фиксированным количеством элементов, включая, как минимум, Proc и регулярное выражение. Моя цель - сопоставить регулярные выражения и выполнить Procs в моей специальной области, где Proce будет задействовать некоторые локальные переменные, которые должны быть определены позже. И затем я встретил проблему выше.
На самом деле я полагаю, что это не то же самое, что и этот вопрос, поскольку мой - как передать привязку к Proc, а не как его передать.

@Niklas: Получил ваш ответ, я думаю, именно этого я и хочу. Он отлично решил мою проблему.

4b9b3361

Ответ 1

Вы можете попробовать следующий взлом:

class Proc
  def call_with_vars(vars, *args)
    Struct.new(*vars.keys).new(*vars.values).instance_exec(*args, &self)
  end
end

Используется следующим образом:

irb(main):001:0* lambda { foo }.call_with_vars(:foo => 3)
=> 3
irb(main):002:0> lambda { |a| foo + a }.call_with_vars({:foo => 3}, 1)
=> 4

Это не очень общее решение. Было бы лучше, если бы мы могли дать ему экземпляр Binding вместо хеша и сделать следующее:

l = lambda { |a| foo + a }
foo = 3
l.call_with_binding(binding, 1)  # => 4

Используя следующий, более сложный хак, это точное поведение может быть достигнуто:

class LookupStack
  def initialize(bindings = [])
    @bindings = bindings
  end

  def method_missing(m, *args)
    @bindings.reverse_each do |bind|
      begin
        method = eval("method(%s)" % m.inspect, bind)
      rescue NameError
      else
        return method.call(*args)
      end
      begin
        value = eval(m.to_s, bind)
        return value
      rescue NameError
      end
    end
    raise NoMethodError
  end

  def push_binding(bind)
    @bindings.push bind
  end

  def push_instance(obj)
    @bindings.push obj.instance_eval { binding }
  end

  def push_hash(vars)
    push_instance Struct.new(*vars.keys).new(*vars.values)
  end

  def run_proc(p, *args)
    instance_exec(*args, &p)
  end
end

class Proc
  def call_with_binding(bind, *args)
    LookupStack.new([bind]).run_proc(self, *args)
  end
end

В основном мы определяем себя как стек поиска вручную, и instance_exec наш proc против него. Это очень гибкий механизм. Он не только позволяет реализовать call_with_binding, но также может использоваться для создания гораздо более сложных цепочек поиска:

l = lambda { |a| local + func(2) + some_method(1) + var + a }

local = 1
def func(x) x end

class Foo < Struct.new(:add)
  def some_method(x) x + add end
end

stack = LookupStack.new
stack.push_binding(binding)
stack.push_instance(Foo.new(2))
stack.push_hash(:var => 4)

p stack.run_proc(l, 5)

Отпечатает 15, как ожидалось:)

UPDATE: Код теперь также доступен в Github. Я тоже использую это для моих проектов.

Ответ 2

class Proc
    def call_with_obj(obj, *args)
        m = nil
        p = self
        Object.class_eval do
            define_method :a_temp_method_name, &p
            m = instance_method :a_temp_method_name; remove_method :a_temp_method_name
        end
        m.bind(obj).call(*args)
    end
end

И затем используйте его как:

class Foo
    def bar
        "bar"
    end
end

p = Proc.new { bar }

bar = "baz"

p.call_with_obj(self) # => baz
p.call_with_obj(Foo.new) # => bar

Ответ 3

аналогичным образом:

class Context
  attr_reader :_previous, :_arguments

  def initialize(_previous, _arguments)
    @_previous = _previous
    @_arguments = _arguments
  end
end

def _code_def(_previous, _arguments = [], &_block)
  define_method("_code_#{_previous}") do |_method_previous, _method_arguments = []|
    Context.new(_method_previous, _method_arguments).instance_eval(&_block)
  end
end

_code_def('something') do
  puts _previous
  puts _arguments
end