Является ли 'eval' единственным способом взаимодействия с объектами связывания в Ruby? - PullRequest
17 голосов
/ 22 июня 2010

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

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

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

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

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

Ответы [ 4 ]

8 голосов
/ 22 июня 2010

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

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

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

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

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

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

Установка переменной внутрипривязка дана 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) }
3 голосов
/ 22 июня 2010

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

jim@linux-g64g:~> 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 будет написано:

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

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

// ctwc / irb / workspace.rb
eval (операторы, @binding) #, файл, строка)

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

1 голос
/ 05 ноября 2010

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

Я собираюсь попытаться угадать ваш типичный вариант использования. Учитывая хэш: {: 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) Установка локального значения не так естественна, потому что для получения вызова метода требуется "self". Не уверен, что есть обходной путь для этого.

4) Блок eval может связываться с хэшем @vars.

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

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

proc { $SAFE = 1; eval "do_some_stuff" }.call  # returns the value of eval call
0 голосов
/ 08 ноября 2010

Вот код для решения на основе хеша.

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

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

...