В Ruby нет возможности динамически определить локальную переменную в текущем контексте?

Мне интересно, есть ли метод, который позволит мне динамически определять ранее неопределенную переменную в текущем контексте. Например:

foo # => NameError: undefined method or local variable ...
# Some method call which sets foo = 1 in the local context
foo # => 1

Другими словами, учитывая, что foo не определено, я ищу любой код, который позволил бы мне определить локальную переменную foo без использования переменной foo (например, если бы у меня была какая-то другая переменная bar, значение которой было :foo, и я должен был полагаться на что установить значение foo).

Кажется, что eval('foo = 1') или eval('foo = 1', binding) или, в Ruby 2.1, binding.local_variable_set(:foo, 1) эквивалентны:

1.times do
  foo = 1
end

другими словами, они устанавливают foo в контексте нового локального контекста, так что значение недоступно вне этого контекста.

Возможно ли то, что я хочу сделать?

Обновление: этот вопрос не относится к какому-либо конкретному контексту локальной переменной (модуль/класс, метод, процедура, блок и т. д.). Мне было бы интересно точно узнать любой контекст, в котором это можно или нельзя сделать.


person Peter Alfvin    schedule 11.10.2013    source источник
comment
в чем проблема, которую вы пытаетесь решить? что бы вы ни пытались сделать, это, вероятно, возможно в ruby, но я не думаю, что это хорошо.   -  person phoet    schedule 11.10.2013
comment
Не то же самое, но, возможно, define_method(:foo) { 1 } вариант?   -  person spickermann    schedule 11.10.2013
comment
Я знаю, что это включает в себя переменную область видимости, но я не понимаю вашего вопроса.   -  person icantbecool    schedule 11.10.2013
comment
@phoet Я спрашиваю о языке Ruby независимо от какой-либо необходимости. Вопрос возник в контексте другого вопроса SO.   -  person Peter Alfvin    schedule 11.10.2013
comment
@spickermann Я согласен, что это было бы похоже, но не то же самое. :-)   -  person Peter Alfvin    schedule 11.10.2013
comment
@icantbecool Не уверен, что это поможет, но я обновил вопрос, включив в него другой способ его формулировки.   -  person Peter Alfvin    schedule 11.10.2013
comment
RE ваш комментарий к моему ответу. Предположительно, ответ может быть «да» во всех контекстах, «да» в некоторых контекстах, «нет» в других или никогда!». Читателям может быть полезно попытаться сузить возможные ответы, пытаясь доказать или опровергнуть утверждение о том, что локальная переменная может быть добавлена ​​в определенном контексте, например, в класс, метод или блок.   -  person Cary Swoveland    schedule 12.10.2013
comment
@CarySwoveland Хорошо, я обновлю вопрос.   -  person Peter Alfvin    schedule 12.10.2013
comment
Поскольку блок не является объектом, я думаю, мы можем смело исключить его. В более общем плане, я думаю, что если бы это было возможно для любого объекта, у нас были бы методы объекта local_variable_set и .._get (так в оригинале), чтобы они соответствовали instance_variable_set и .._get.   -  person Cary Swoveland    schedule 12.10.2013
comment
@CarySwoveland Я не понимаю, как блок, не являющийся объектом, имеет какое-либо отношение к возможности создавать локальную переменную внутри блока. Учитывая, что Kernel#binding якобы возвращает текущий контекст, это действительно все, что вам нужно для ссылки на текущий контекст.   -  person Peter Alfvin    schedule 12.10.2013
comment
модуль ядра включен в класс Object, поэтому его методы доступны в каждом объекте Ruby.   -  person Cary Swoveland    schedule 12.10.2013
comment
@CarySwoveland Верно, это моя точка зрения. :-) Я отвечал на ваш комментарий, что мы можем исключить блоки, потому что они не являются объектами. А в Ruby 2.1 есть local_variable_set, но он ведет себя точно так же, как eval, согласно ruby-doc.org/core-trunk/   -  person Peter Alfvin    schedule 12.10.2013
comment
Очень интересно. Не знал о тех, что были в 2.1. Хороший выбор имен. Пара вариантов использования Binding#local_variable_get и _set дана в документации ruby-truck для тех, кто методы.   -  person Cary Swoveland    schedule 12.10.2013


Ответы (3)


Кажется, что магия Ruby предоставит способ, но, по словам Матца, это было возможно только в 1.8 через eval и только в определенных контекстах (например, irb). Начиная с 1.9, это поведение было убрано («строго запрещено»):

Сам Матц взвешивается здесь: https://www.ruby-forum.com/topic/155673#685906

Я где-то читал, что теперь Ruby не может динамически создавать локальные переменные. Это правда или просто ошибка?

Локальные переменные создаются во время компиляции, поэтому локальные переменные, определенные в eval(), недоступны вне eval. В версии 1.8 irb и tryruby выполняют построчную компиляцию, так что локальные переменные выбрасываются из eval(), но в версии 1.9 это строго запрещено даже при построчной компиляции.

          matz.

(Здесь альтернатива, не связанная с последовательностью, для всех, кто хочет чего-то подобного, но не точной технической ситуации, которая есть у спрашивающего):

Используйте хэш:

local_hash = {}

my_vars.each_pair do |k,v|
   local_hash[k] = v
end

puts local_hash['foo']
#=> 'baz'
person dancow    schedule 11.10.2013
comment
Если вы обновите свой ответ, просто сказав «Нет, это невозможно» и предоставив ссылку на Matz, я буду рад принять это. Это предполагает, что в дополнение к тому, что это невозможно с eval, нет другого способа выполнить это в свете объяснения Маца о том, что локальные переменные создаются только во время компиляции. - person Peter Alfvin; 11.10.2013
comment
К сожалению, я не могу этого сделать... Я знаю только объяснение Маца, но не могу сказать вам наверняка, что только потому, что он был остановлен с помощью eval, это нельзя сделать другим методом (кроме запуска Ruby 1.8). Так что я согласен оставить дверь открытой, а не ошибиться мастером Ruby-хакером :) - person dancow; 11.10.2013
comment
Хорошо, как насчет того, чтобы хотя бы удалить комментарий «Я уверен, что есть способ», и в этом случае я поставлю +1 за ссылку на Matz. :-) Хотя они могут быть способом, я не думаю, что у вас есть какая-то причина быть уверенным, и я бы не хотел давать кому-либо ложную надежду. В связи с этим, как видно из моих комментариев к вопросу, я не пытаюсь решить какую-либо основную проблему; Я просто пытаюсь улучшить свое понимание языка и основных библиотек. Таким образом, ваше предложение, касающееся хэша, не очень актуально, и, с моей точки зрения, его лучше удалить. - person Peter Alfvin; 11.10.2013
comment
Готово. Но я надеюсь, что кто-то докажет, что я ошибаюсь, и придумает какой-нибудь умный хак, хотя я не могу представить, что это может быть, учитывая объяснение Маца. - person dancow; 12.10.2013
comment
Принято и плюс 1. :-) - person Peter Alfvin; 12.10.2013
comment
@PeterAlfvin Проблема с этим умственным упражнением заключается в том, что вы ограничиваете решение техникой, а не обнаруживаете технику, которая дает решение. Если бы вместо этого вы пытались решить реальную проблему, хэш-решение было бы вполне приемлемым. Суть здесь в том, чтобы найти технику, которая решает проблему, и именно это она и делает. - person Richard_G; 11.06.2015
comment
@R_G Если бы я пытался решить реальную проблему, я бы опубликовал эту реальную проблему. Меня интересовал исключительно этот конкретный аспект языка, и в этом контексте, в контексте вопроса, любой другой метод хранения и извлечения значений, как заметил Дэн, не имеет смысла. - person Peter Alfvin; 12.06.2015

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

>> my_lv = 0
=> 0
>> instance_eval("#{'my_lv'} = 42")
=> 42
>> my_lv
=> 42

Таким образом, просто создайте из собранного ввода (из gets, пережевывая или разделяя по мере необходимости, он просто естественным образом закончится как строка) и вызовите to_sym на нем, и вставьте новый символ в local_variables и eval прочь...

>> local_variables << :my_created_lv
=> [:my_lv,
 :__,
 :_,
 :_dir_,
 :_file_,
 :_ex_,
 :_pry_,
 :_out_,
 :_in_,
 :my_created_lv]
>> 

Затем вы берете собранную строку, преобразованную в символ и назначенную в коде, показанном выше, и оцениваете ее, чтобы получить значение.

>> eval :my_lv.to_s
>> 24

Как отмечено в другом ответе, я не могу легко воспроизвести это за пределами Pry или IRB.

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

person vgoff    schedule 11.10.2013
comment
Я спрашиваю о случае, когда переменная еще не определена. Нет проблем, когда переменная уже определена, и вы просто хотите изменить значение. - person Peter Alfvin; 12.10.2013
comment
Вот почему я использовал строку внутри интерполяции. Остальное академично. - person vgoff; 12.10.2013
comment
И работает только в REPL, по-видимому. :( - person vgoff; 12.10.2013
comment
Извините, я не следил ни за одним из ваших последних двух комментариев. - person Peter Alfvin; 12.10.2013
comment
Я взял команды, которые я написал в ответе, и написал обычную программу Ruby, чтобы попытаться убедиться, что она по-прежнему работает как обычная программа Ruby, а не построчная оценка, и она не может работать в обычной программе Ruby. REPL означает «Чтение — Оценка — Печать — Цикл». Предыдущее просто означало, что строка внутри интерполяции могла быть получена несколькими способами, возможно, gets.chomp. В любом случае, победить в Ruby 1.9 и более поздних версиях будет непросто. - person vgoff; 12.10.2013
comment
то же, что и binding.eval, и не может объявлять несуществующую переменную - person akostadinov; 08.09.2014

Подойдет ли вам переменная экземпляра класса?

class Cat
end
Cat.instance_variable_set '@last_words', 'meow, meow, me...'
Cat.instance_variable_get '@last_words' # => "meow, meow, me..."
Cat.new.instance_variable_get '@last_words' # => nil

Если нет, расскажите о контексте и о том, как вы будете использовать локальную переменную.

person Cary Swoveland    schedule 11.10.2013
comment
Мой вопрос не зависит от того, где находится локальная переменная (класс, метод, блок и т. д.). И, судя по моему предыдущему комментарию к самому вопросу, я не пытаюсь решить основной проблемы. Я просто задаю вопрос о языке и основных библиотеках, связанных с объявлением локальной переменной, чтобы лучше понять. - person Peter Alfvin; 12.10.2013