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

Является ли "eval" единственным способом взаимодействия с объектами привязки в Ruby?

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

Может быть, я просто пурист из другой школы, но у меня аллергия на строковые конструкции "eval", вообще говоря. Есть ли способ сделать одно из следующего, надежно и в общем случае, с учетом объекта привязки:

  • Перечислите идентификаторы в области видимости в контексте, представляющем привязку, или получите хэш содержимого.
  • Задайте значение локальной переменной в привязке, равную значению некоторой локальной переменной во внешнем контексте. В идеале это должно работать в целом, даже если значение является ссылкой на объект, файловым дескриптором или каким-либо другим сложным объектом.
  • (extension 2:) Если задан хэш, задайте locals в привязке для каждой записи.
  • Еще лучше, учитывая хэш, строит привязку только с базовыми языковыми конструкциями и именами в хэш-области.

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

В качестве альтернативы, существует ли какой-либо способ eval-кода, который уже был проанализирован в контексте привязки, подобно синтаксису BLL-кода Perl eval?

4b9b3361

Ответ 1

В поисках больше я нашел ответ хотя бы на часть моего вопроса:

На основе: http://wikis.onestepback.org/index.cgi/Tech/Ruby/RubyBindings.rdoc/style/print

Остальное - от экспериментов после полезных подсказок Джима Шуберта.

  • Это может быть выполнено eval -ing local_variables, instance_variables и global_variables внутри привязки.
  • Вы можете сделать что-то, как описано ниже, с учетом var_name, new_val, my_binding (синтаксис может быть несовершенным или улучшаемым, не стесняйтесь предлагать в комментариях. Кроме того, я не мог заставить форматирование кода работать внутри списка также будут реализованы предложения о том, как это сделать.)
  • Вы можете прямо взять (2) и зациклировать хеш для этого.
  • См. второй блок кода ниже. Идея состоит в том, чтобы начать с TOPLEVEL_BINDING, который, как я считаю, обычно включает в себя глобальные переменные.

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

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

Установка переменной внутри привязки заданной var_name, new_val, my_binding:

# the assignment to nil in the eval coerces the variable into existence at the outer scope
setter = eval "#{var_name} = nil; lambda { |v| #{var_name} = v }", my_binding
setter.call(new_val)

Создание привязки "на заказ":

my_binding = eval "lambda { binding }", TOPLEVEL_BINDING # build a nearly-empty binding
# set_in_binding is based on the above snippet
vars_to_include.each { |var_name, new_val| set_in_binding(var_name, new_val, my_binding) }

Ответ 2

Уолтер, вы должны иметь возможность напрямую взаимодействовать с привязкой. Я раньше не работал с привязками, но я запустил пару вещей в irb:

[email protected]:~> irb
irb(main):001:0> eval "self", TOPLEVEL_BINDING
=> main
irb(main):002:0> eval "instance_variables", TOPLEVEL_BINDING
=> []
irb(main):003:0> eval "methods", TOPLEVEL_BINDING
=> ["irb_kill", "inspect", "chws", "install_alias_method", ....

У меня также есть Metaprogramming Ruby, который не говорит о привязке. Однако, если вы заберете это, в конце страницы 144 говорится:

В каком-то смысле вы можете видеть привязку объекты как "более чистые" формы замыканий чем блоки, потому что эти объекты содержат область действия, но не содержат код.

И на противоположной странице предлагается использовать код irb (удаление последних двух аргументов для вызова eval), чтобы увидеть, как он использует привязки:

//ctwc/irb/workspace.rb
eval (statements, @binding) #, file, line)

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

Ответ 3

Не могли бы вы объяснить, что именно вы пытаетесь сделать? Просьба представить код, показывающий, как вы хотите, чтобы он работал. Там может быть лучший и безопасный способ выполнить то, что вы хотите.

Я собираюсь сделать попытку угадать ваш типичный случай использования. Учитывая хеш: {: a = > 11,: b = > 22}

Вам нужна минимальная, относительно изолированная среда исполнения, в которой вы можете получить доступ к значениям хэша в качестве локальных переменных. (Я не совсем уверен, почему вам нужны они, чтобы быть местными, за исключением, может быть, если вы пишете DSL, или если у вас уже написанный код, который вы не хотите адаптировать для доступа к Hash.)

Здесь моя попытка. Для простоты я предполагаю, что вы используете символы только как хэш-ключи.

class MyBinding
  def initialize(varhash); @vars=varhash; end
  def method_missing(methname, *args)
    meth_s = methname.to_s
    if meth_s =~ /=\z/
      @vars[meth_s.sub(/=\z/, '').to_sym] = args.first
    else
      @vars[methname]
    end
  end
  def eval(&block)
    instance_eval &block
  end
end

Пример использования:

hash = {:a => 11, :b => 22}
mb = MyBinding.new hash
puts mb.eval { a + b }
# setting values is not as natural:
mb.eval { self.a = 33 }
puts mb.eval { a + b }

Некоторые оговорки:

1) Я не повышал NameError, если переменная не существовала, но простые исправления для замещения, которые:

def initialize(varhash)
  @vars = Hash.new {|h,k| raise NameError, "undefined local variable or method `#{k}'" }
  @vars.update(varhash)
end

2) Нормальные правила определения области видимости таковы, что если локальный существует, имя которого совпадает с именем метода, то локаль имеет приоритет, если вы явно не вызываете вызов метода, например a(). Класс выше имеет противоположное поведение; метод имеет приоритет. Чтобы получить "нормальное" поведение, вам нужно скрыть все (или большинство) стандартных методов, например #object_id. ActiveSupport предоставляет для этой цели класс BlankSlate; он поддерживает только 3 метода:

 __send__, instance_eval, __id__

. Чтобы использовать его, просто сделайте MyBinding наследованием от BlankSlate. Помимо этих 3 методов, вы также не сможете иметь локальных жителей с именем "eval" или "method_missing".

3) Настройка локального не так естественно, потому что для получения вызова метода требуется "я". Не уверен, есть ли способ для этого.

4) Блок eval может испортиться с хэшем @vars.

5) Если у вас есть реальный локальный var в области, где вы называете mb.eval, с тем же именем, что и один из хэш-ключей, реальный локальный будет иметь приоритет... это, вероятно, самый большой недостаток, потому что тонкий ошибки могут закрашиваться.

После реализации недостатков моей техники я рекомендую использовать Hash напрямую, чтобы сохранить набор переменных, если только я не вижу причин иначе. Но если вы все еще хотите использовать собственный eval, вы можете повысить безопасность с помощью Regexps во избежание ввода кода, а "локальная" настройка $SAFE будет выше для eval-вызова с помощью Proc, например:

proc { $SAFE = 1; eval "do_some_stuff" }.call  # returns the value of eval call

Ответ 4

Здесь приведен код для решения на основе Hash.

class ScopedHash
  def initialize(varhash)
    # You can use an OpenStruct instead of a Hash, but the you lose the NameError feature.
    # OpenStructs also don't have the ability to list their members unless you call a protected method
    @vars = Hash.new {|h,k| raise NameError, "undefined local variable or method `#{k}'" }
    @vars.update(varhash)
  end
  def eval
    yield @vars
  end
end

if __FILE__ == $0
  # sample usage
  hash = {:a => 11, :b => 22}
  sh = ScopedHash.new hash
  puts sh.eval {|v| v[:a] + v[:b] }
  sh.eval {|v| v[:a] = 33 }
  puts sh.eval {|v| v[:a] + v[:b] }
  sh.eval{|v| v[:c] }  # raises NameError
end

Итак, вместо использования локальных жителей вы просто получите доступ к полученному Hash. Я думаю, что существует очень мало причин, по которым человек будет вынужден манипулировать объектом Binding; обычно есть более чистые способы выполнения задачи.